devs4wl
devs4wl

Reputation: 3

Service in Spring not setting values with Junit

Im testing my service with Junit but the result is not the expected. When i save my entity, the return date is not setted in service.

Test:

@Test
@DisplayName("Should set determined time for return date")
public void shouldSetReturnDate() {

    ClientDTORequest dto = createNewDTOClient();
    Client client = createNewClient();

    Mockito.when(clientRepository.save(Mockito.any())).thenReturn(client);
    Client saved = clientService.save(dto);

    Assertions.assertEquals(dateTimeNow.plusMinutes(30), saved.getReturnDate());
}

My createNewClient():

private Client createNewClient() {
    //the null param is the return date
    return new Client(1L, "name", null);
}

My service:

    public Client save(ClientDTORequest dto) {

        Client client = mapper.map(dto, Client.class);
        client.setReturnDate(dateTimeNow.plusMinutes(30));
        Client savedClient = clientRepository.save(client);

        return savedClient;
    }

And when the test result:

org.opentest4j.AssertionFailedError: 
Expected :2022-04-04T01:17:25.715895900
Actual   :null

The result is not passed by the service to mock, this is my shot, but i dont know why.

Thanks!

Upvotes: 0

Views: 413

Answers (3)

devs4wl
devs4wl

Reputation: 3

After some hours of testing i found the problem:

My service was changing data, but was overridden by my mock:

Mockito.when(clientRepository.save(Mockito.any())).thenReturn(client); <-- mock overridden the changed data from service
Client saved = clientService.save(dto);

So i found ArgumentCaptor, where i can get the object from method call:

Declaring the Captor:

@Captor
ArgumentCaptor<Client> clientCaptor;

Using at test method:

Mockito.when(clientRepository.save(clientCaptor.capture())).thenReturn(client); //<-- capturing the result
clientService.save(dto);
Client saved = clientCaptor.getValue() //getting object

Assertions.assertEquals(dto.getReturnDate().plusMinutes(30), saved.getReturnDate()); //assertion

Upvotes: 0

Leo Tapia
Leo Tapia

Reputation: 234

The problem is you're coupled to "now", so the service always will have the time at the moment it runs. One of the best ways of work with time is by modeling the concept Clock or TimeProvider and injecting it to the Service. Then you can mock it to assert the time in the test.

class Clock {
    LocalDateTime now() {
        return LocalDateTime.now().plusMinutes(30); // <-- as you needs
    }
}

class Service {

    private Clock clock;

    Service(Clock clock) {
        this.clock = clock;
    }

    void save(MyEntity entity) {
        entity.setCreatedDateTime(clock.now());
        //repositoty.save(entity);
    }
}

@Getter
class MyEntity {
    private LocalDateTime createdDateTime;

    public void setCreatedDateTime(LocalDateTime createdDateTime) {
        //assing it to a field
        this.createdDateTime = createdDateTime;
    }
}

class ServiceTest {

    @Mock
    private Clock clock;

    private Service service;

    @Test
    void testSave() {
        LocalDateTime fixedDateTimeNow = LocalDateTime.of(2022, 4, 3, 18, 0, 0);
        Mockito.when(clock.now()).thenReturn(fixedDateTimeNow);

        MyEntity entity = new MyEntity();
        service.save(entity);

        Assertions.assertEquals(fixedDateTimeNow, entity.getCreatedDateTime());
    }
}

Note: Be careful about holding state in your service, so it's not thread safe. So, you'll end up with concurrency problems when multiple calls to service occurs "at the same time".

Upvotes: 1

user3184550
user3184550

Reputation:

If you injected your clientRepository with @Autowired then it won't mock. Try @SpyBean (@Autowired ClientRepository clientRepository wouldn't mock; @SpyBean ClientRepository clientRepository should mock)

Upvotes: 0

Related Questions