Mockito 在内部调用模拟方法时失败

Mockito failing inside internal call for mocked method

我正在尝试使用 mockito 的 when 调用来模拟方法的 return 值。但是,我对此并不陌生,我可能会误解 mockito 的工作原理,因为当调用另一个方法时,调用在模拟的方法内部失败。我认为无论该方法是如何实现的,我都应该得到我要求的 return 值?或者我是否还需要模拟该方法的内部结构?我觉得不应该。

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;
}

我的测试:

@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, ""));
}

更新

尝试使用 anyString() 而不是 eq()。

谢谢。

错误在这里: when(cookieSessionUtils.getEdUserId(eq("anyString()"), eq("anyString()"))).thenReturn(eq(String.valueOf(uuid)));

它应该是这样的 when(cookieSessionUtils.getEdUserId(anyString()), anyString()).thenReturn(uuid); 请参考Argument matchers.

的Mockito文档

因为参数匹配器寻找字符串“anyString()”,它们从不匹配方法调用提供的实际参数,因此永远不会返回您期望的 uuid。

你的测试有几个地方坏了。

对真实对象设定期望

您应该在模拟和间谍上调用 Mockito.when,而不是在被测系统上。 Mockito 通常会用明确的错误消息报告它,但是你从 getEdUserId 抛出 NPE,因此改为报告。 NPE 源于这样一个事实,即 eq 和 anyString return null,它被传递给真正的方法。

匹配器的使用无效

正如@StefanD 在他的回答中解释的那样 eq("anyString()") 不匹配任何字符串。它只匹配一个字符串“anyString()”

返回数学而不是真实对象

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

这是匹配器的非法位置。

在 WebMvcTest 中混合 Mockito 和 Spring 注释

这是一个常见错误。 Mockito 不会将 bean 注入 spring 上下文。

根据提供的代码,不清楚 CookieSessionUtils 是什么(Controller?ControllerAdvice?)以及测试它的正确方法是什么。

更新

看来您正在尝试替换一些正在测试的方法。一种方法是使用间谍。 参见 https://towardsdatascience.com/mocking-a-method-in-the-same-test-class-using-mockito-b8f997916109

用这种风格写的测试:

@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, ""));
    }
}

另一种方法是调用真正的方法,但模拟所有协作者:StateUtils 和 sifAuthorizationService。如果你想测试 public getEdUserId.

,我可能会选择这个

模拟协作者时编写的测试:

@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, ""));
    }
}

我假设 StateUtils.checkJWT 不需要被嘲笑

以上几点仍然有效,无论哪种情况都需要解决。

备注

  • 由于被测系统目前是一个服务,我建议放弃 WebMvcTest 并使用普通 mockito 来测试它。
  • SUT 应该是一项服务吗?在过滤器中处理授权代码更典型。
  • 注意在间谍上存根方法时 doReturn 的用法。
  • 您在比需要更多的地方使用模拟。例如 Cookie 构造起来很简单,使用 mock
  • 没有意义