【问题标题】:Mockito failing inside internal call for mocked methodMockito 在内部调用模拟方法时失败
【发布时间】:2022-01-25 19:07:23
【问题描述】:

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

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... -> when(cookieSessionUtilsMocked...cookieSessionUtils.verifyState -> cookieSessionUtilsMocked.verifyState。顺便说一句,如果您正在测试 CookieSessionUtils 本身,为什么要嘲笑它?您需要在测试类中模拟服务,而不是类本身
  • 不是错字。被测方法和要模拟的方法都在同一个类下,所以我尝试使用正在自动装配的类和模拟版本调用一个。
  • 它不是那样工作的。 Lesiak 是对的,你不能将 Mockito 注入到 spring 上下文中

标签: java spring-boot unit-testing mockito spring-test


【解决方案1】:

你的测试在几个地方被破坏了。

对真实对象设定期望

您应该在模拟和间谍上调用 Mockito.when,而不是在被测系统上。 Mockito 通常会以明确的错误消息报告它,但您会从getEdUserId 抛出 NPE,因此会报告此错误。 NPE 源于 eq 和 anyString 都返回 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 应该是一项服务吗?在过滤器中处理身份验证代码更为典型。
  • 注意在对 spy 进行方法存根时使用 doReturn
  • 您在比需要更多的地方使用模拟。例如Cookie 构造起来很简单,使用模拟没有意义

【讨论】:

  • CookieSessionUtils 是一项服务。被测方法和辅助方法都在同一个类上,这就是我在 cookieServiceUtils 类下模拟的原因。尝试使用 autowired 或 mockBean 调用模拟方法。
  • 感谢您的更新。我正在阅读它并尝试消化提供的所有信息,因为我对模拟世界还是新手。如果我可以使用这些建议或更新可能阻碍我的内容,我会回到这里。
  • 我能看到的一个小障碍是StateUtils.checkJWT 是一个静态方法 - 这些有点难以模拟 - 通常将它们作为实例方法放在 bean 中并注入 bean。
  • 但是我也需要模拟那个吗?我想当我在嘲笑父 getEdUserId() 方法时,我不需要关心它里面的其他方法吗?
  • 如果你遵循 Spy 方法,只模拟 getEdUserId()。如果您只模拟合作者并调用真正的 getEdUserId() ,那么您需要模拟对外部服务的调用。对于实用方法(尤其是静态方法),您需要决定 - 检查它们的正常代码在测试上下文中是否有意义。
【解决方案2】:

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

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

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

【讨论】:

  • 您好,感谢您的回答。是的,我也尝试过,但它也会失败。我将添加失败的屏幕截图。
  • 在 .thenReturn 部分中,您仍在使用无效语法。这部分应该是thenReturn(uuid),但你使用的是.thenReturn(String.valusOf(uuid))
  • 该方法返回一个字符串,所以我无法返回之前在行上创建的 uuid 对象而不进行包装。
  • 也试过在方法String uuid = String.valueOf(UUID.randomUUID()); when(cookieSessionUtils.getEdUserId(anyString(), anyString())).thenReturn(uuid);之外包装
  • 好的。还没有认识到 uuid 是 UUID 类型……我的错。您的模拟类名为 cookieSessionUtilsMocked 但您使用的是 cookieSessionUtils。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-07
相关资源
最近更新 更多