Francislainy Campos
Francislainy Campos

Reputation: 4136

Mockito failing inside internal call for mocked method

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.

Upvotes: 0

Views: 2625

Answers (2)

Lesiak
Lesiak

Reputation: 25936

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

Upvotes: 1

Stefan D.
Stefan D.

Reputation: 289

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.

Upvotes: 0

Related Questions