0

I'm trying to mock the return value for a method using the when call from mockito. However, I'm new to this and I may perhaps be misunderstanding how mockito works, since the call is failing inside the method mocked when that calls another method. I thought regardless of how that method is implemented, I should be getting the return value I'm asking for? Or do I need to mock also the internals for that method? I feel that shouldn't be it.

public boolean verifyState(HttpServletRequest request, String s) {

    String stateToken = getCookieByName(request, STATE_TOKEN);
    String authToken = getCookieByName(request, AUTHN);

    boolean isValidState = true;

    if (isValidState) {
        
        try {
            log.info(getEdUserId(stateToken, authToken));

            return true;
        } catch (Exception e) {
            ExceptionLogger.logDetailedError("CookieSessionUtils.verifyState", e);
            return false;
        }
    } else {
        return false;
    }
}

public String getEdUserId(String stateToken, String authToken) throws Exception {
    String edUserId;
    Map<String, Object> jwtClaims;
    jwtClaims = StateUtils.checkJWT(stateToken, this.stateSharedSecret); // Failing here not generating a proper jwt token
    log.info("State Claims: " + jwtClaims);
    edUserId = sifAuthorizationService.getEdUserIdFromAuthJWT(authToken);
    return edUserId;
}

My test:

@ActiveProfiles(resolver = MyActiveProfileResolver.class)
@WebMvcTest(value = CookieSessionUtils.class, includeFilters = {
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ApiOriginFilter.class, ValidationFilter.class})})
class CookieSessionUtilsTest {

@Autowired
private CookieSessionUtils cookieSessionUtils; // Service class

@Mock
private CookieSessionUtils cookieSessionUtilsMocked; // Both the method under test and the one mocked are under the same class, so trying these two annotations together.

@Mock
private HttpServletRequest request;

@BeforeEach
public void setUp() {
    MockitoAnnotations.initMocks(this);
}

@Test
public void testVerifyState1() throws Exception {

    //...Some mocks for getCookieName

    UUID uuid = UUID.randomUUID();
    when(cookieSessionUtils.getEdUserId(anyString(), anyString()).thenReturn(eq(String.valueOf(uuid))); // When this line runs it fails on verifyState method

    assertTrue(cookieSessionUtils.verifyState(request, ""));
}

enter image description here

UPDATE

Attempt using anyString() instead of eq().

enter image description here

Thank you.

Francislainy Campos
  • 3,462
  • 4
  • 33
  • 81
  • Your code has a typo: `when(cookieSessionUtils...` -> `when(cookieSessionUtilsMocked...` and `cookieSessionUtils.verifyState` -> `cookieSessionUtilsMocked.verifyState`. BTW, if you are testing the `CookieSessionUtils` itself, why do you mock it? You need to mock services inside the testing class, not the class itself – Valijon Dec 26 '21 at 10:47
  • Not a typo. Both the method under test and the one to be mocked are under the same class, so I was trying to call one using the class being autowired and the one with the mocked version. – Francislainy Campos Dec 26 '21 at 10:50
  • It does not work like that. Lesiak is right, you cannot inject Mockito into spring context – Valijon Dec 26 '21 at 10:55

2 Answers2

1

Your test is broken in a few places.

Setting expectations on a real object

You should call Mockito.when on mocks and spies, not on System under test. Mockito normally reports it with a clear error message, but you throw a NPE from getEdUserId, so this is reported instead. The NPE stems from the fact that both eq and anyString return null, which is passed to the real method.

Invalid use of matchers

As @StefanD explained in his answer eq("anyString()") is not matching any string. It matches only one string "anyString()"

Returning a mather instead of real object

thenReturn(eq(String.valueOf(uuid)))

This is illegal position for a matcher.

Mixing Mockito and Spring annotations in a WebMvcTest

This is a common error. Mockito does not inject beans to the spring context.

From the code provided it is unclear what CookieSessionUtils is (Controller? ControllerAdvice?) and what is the correct way to test it.

Update

It seems that you are trying to replace some methods under test. A way to do it is to use a Spy. See https://towardsdatascience.com/mocking-a-method-in-the-same-test-class-using-mockito-b8f997916109

The test written in this style:

@ExtendWith(MockitoExtension.class)
class CookieSessionUtilsTest {

    @Mock
    private HttpServletRequest request;

    @Mock
    private SifAuthorizationService sifAuthorizationService;

    @Spy
    @InjectMocks
    private CookieSessionUtils cookieSessionUtils;

    @Test
    public void testVerifyState1() throws Exception {
        Cookie cookie1 = new Cookie("stateToken", "stateToken");
        Cookie cookie2 = new Cookie("Authn", "Authn");
        when(request.getCookies()).thenReturn(new Cookie[]{cookie1, cookie2});

        UUID uuid = UUID.randomUUID();
        doReturn(String.valueOf(uuid)).when(cookieSessionUtils).getEdUserId(anyString(), anyString());

        assertTrue(cookieSessionUtils.verifyState(request, ""));
    }
}

An alternative way is to call the real method, but to mock all collaborators: StateUtils and sifAuthorizationService. I would probably go with this one, if you want to test public getEdUserId.

Test written when mocking collaborators:

@ExtendWith(MockitoExtension.class)
class CookieSessionUtilsTest {

    @Mock
    private HttpServletRequest request;

    @Mock
    private SifAuthorizationService sifAuthorizationService;

    @InjectMocks
    private CookieSessionUtils cookieSessionUtils;

    @Test
    public void testVerifyState1() throws Exception {
        Cookie cookie1 = new Cookie("stateToken", "stateToken");
        Cookie cookie2 = new Cookie("Authn", "Authn");
        when(request.getCookies()).thenReturn(new Cookie[]{cookie1, cookie2});

        UUID uuid = UUID.randomUUID();
        when(sifAuthorizationService.getEdUserIdFromAuthJWT(cookie2.getValue())).thenReturn(String.valueOf(uuid));

        assertTrue(cookieSessionUtils.verifyState(request, ""));
    }
}

I took the assumption that StateUtils.checkJWT does not need to be mocked

The points above are still valid and need to be resolved in either case.

Remarks

  • As the system under test is currently a Service, I suggest to drop WebMvcTest and test it with plain mockito instead.
  • Should SUT be a service? It is more typical to handle auth code in filters.
  • note usage of doReturn when stubbing a method on a spy.
  • You use mocks in more places than needed. For example Cookie is trivial to construct, there is no point in using a mock
Lesiak
  • 22,088
  • 2
  • 41
  • 65
  • CookieSessionUtils is a service. Both the method under test and the helper method are on the same class, that's why I'm mocking under the cookieServiceUtils class. Tried both with autowired or mockBean to call the mocked method. – Francislainy Campos Dec 26 '21 at 10:01
  • Thank you for the update. I'm reading it and trying to digest all the information provided as I'm still new to the mocking world. I'll come back here if I can make it work with these suggestions or with updates on what may be hindering me. – Francislainy Campos Dec 26 '21 at 10:46
  • A slight obstacle I can see is that `StateUtils.checkJWT` is a static method - these are a bit harder to mock - it is typical to place them in a bean as instance methods and inject the bean. – Lesiak Dec 26 '21 at 11:13
  • But do I need to mock that one too? I thought as I'm mocking the parent getEdUserId() method I wouldn't need to care about whatever other methods are inside it? – Francislainy Campos Dec 26 '21 at 11:32
  • If you follow the Spy approach mock only getEdUserId(). If you only mock collaborators and call real getEdUserId() than you need to mock calls to external services. For utility methods (in particular static methods) you need to decide - check whether their normal code makes sense in the testing context. – Lesiak Dec 26 '21 at 11:34
  • I'm trying to keep with the mocking approach but it seems I'm on a never ending loop, as each new inner method mocked calls other methods that also need to be mocked? Also, can I confirm should I use the autowired or mocked version for my class when calling the when methods. – Francislainy Campos Dec 27 '21 at 07:06
  • thank you so so much for this here. The tests are working now and I feel I've got in touch with many new concepts such as spy and doreturn and learned a lot from your input. Really appreciate your help. Thanks again. – Francislainy Campos Dec 30 '21 at 05:02
0

The error is here: when(cookieSessionUtils.getEdUserId(eq("anyString()"), eq("anyString()"))).thenReturn(eq(String.valueOf(uuid)));

It should read like when(cookieSessionUtils.getEdUserId(anyString()), anyString()).thenReturn(uuid); Please refer to the Mockito documentation of Argument matchers.

Because the argument matchers looking for the string "anyString()" they never match the actual parameters the method call is providing and so there is never returned the uuid you expecting.

Stefan D.
  • 299
  • 3
  • 8
  • Hi, thanks for your answer. Yes, I tried that also but it will fail too. I'll add a screenshot of the failure. – Francislainy Campos Dec 26 '21 at 08:51
  • In the .thenReturn part you're still using a invalid syntax. This part should read ```thenReturn(uuid)``` but you're using ```.thenReturn(String.valusOf(uuid))``` – Stefan D. Dec 26 '21 at 09:14
  • The method returns a string so I can't return the uuid object that I've created on the line before without wrapping it. – Francislainy Campos Dec 26 '21 at 09:26
  • Tried also wrapping outside the method ```String uuid = String.valueOf(UUID.randomUUID()); when(cookieSessionUtils.getEdUserId(anyString(), anyString())).thenReturn(uuid);``` – Francislainy Campos Dec 26 '21 at 09:32
  • O.K. Haven't recognized that uuid is of type UUID... my fault. Your mock class is named cookieSessionUtilsMocked but instead of that you're using cookieSessionUtils. – Stefan D. Dec 26 '21 at 09:49
  • I have both classes as one is autowired and the other one mocked to try whether one or the other would work. – Francislainy Campos Dec 26 '21 at 10:00