Home > Mobile >  Mockito. Spy doReturn doesn't change return value
Mockito. Spy doReturn doesn't change return value

Time:01-26

I want to test the method calculateApiTokenExpireDate(). I created a spy for currentDateTime() so that it returns static date. But it still returns the current date. Could someone plese show me where is my mistake? Thanks

public class ServiceHelper {

    static final long DEFAULT_OUTDATED_TIME_MILLIS = 86400000;
    static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm";

    long oneTimeOutDatedMillis = DEFAULT_OUTDATED_TIME_MILLIS;

    protected String getApiTokenExpireDate(){
        DateTimeFormatter customFormat = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
        LocalDateTime tokenExpireDate = calculateApiTokenExpireDate();
        return tokenExpireDate.format(customFormat);
    }

    protected LocalDateTime calculateApiTokenExpireDate(){
        LocalDateTime now = currentDateTime();
        return now.plus(oneTimeOutDatedMillis, ChronoUnit.MILLIS);
    }

    protected LocalDateTime currentDateTime() {
        return LocalDateTime.now();
    }
}
public class ServiceHelperTest {

    private ServiceHelper serviceToTest;

    private ServiceHelper spy;

    @BeforeEach
    void beforeEach() {
        serviceToTest = new ServiceHelper();
        serviceToTest.oneTimeOutDatedMillis = 86400000;
        spy = Mockito.spy(serviceToTest);
    }

    @Test
    void calculateApiTokenExpireDate_return_plus_one_day_date() {
        /* prepare */
        doReturn(LocalDateTime.of(2023, 1, 24, 14, 1)).when(spy).currentDateTime();

        /* execute */
        LocalDateTime tokenExpireDate = serviceToTest.calculateApiTokenExpireDate();

        /* test */
        assertEquals(2023, tokenExpireDate.getYear());
        assertEquals(1, tokenExpireDate.getMonthValue());
        assertEquals(25, tokenExpireDate.getDayOfMonth());
    }
}

CodePudding user response:

Calls to this cannot be spied, because the this reference is not changed. You can change your class to accept in a java.time.Clock instance or on a function/interface which returns the current date/time. Then you can inject Clock#fixed() in your tests.

Class:

public class ServiceHelper {

    static final long DEFAULT_OUTDATED_TIME_MILLIS = 86400000;
    static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm";

    private final Clock clock;

    long oneTimeOutDatedMillis = DEFAULT_OUTDATED_TIME_MILLIS;

    public ServiceHelper(final Clock clock) {
        this.clock = clock;
    }

    // ...

    protected LocalDateTime calculateApiTokenExpireDate(){
        LocalDateTime now = currentDateTime();
        return now.plus(oneTimeOutDatedMillis, ChronoUnit.MILLIS);
    }

    protected LocalDateTime currentDateTime() {
        return LocalDateTime.now(clock);
    }
}

Test:

public class ServiceHelperTest {
    private ServiceHelper serviceToTest;
    private Instant now;

    @BeforeEach
    void beforeEach() {
        /* prepare */
        now = Instant.now(); // or: Instant.parse("...")
        serviceToTest = new ServiceHelper(Clock.fixed(now, ZoneOffset.UTC));
        serviceToTest.oneTimeOutDatedMillis = 86400000;

    }

    @Test
    void calculateApiTokenExpireDate_return_plus_one_day_date() {
        /* execute */
        LocalDateTime tokenExpireDate = serviceToTest.calculateApiTokenExpireDate();

        /* test */
        // assert against this.now ...
    }
}

CodePudding user response:

It looks like you are mocking the wrong instance of the class. In the test case, you are mocking the spy instance of the ServiceHelper class, but you are calling the method on the serviceToTest instance, which is the original "unmocked" object. To fix this, you should call the method on the spy instance, like this:

LocalDateTime tokenExpireDate = spy.calculateApiTokenExpireDate();

Also I would recommend you to read that section from official documentation with quite good examples.

  • Related