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 没有意义
我正在尝试使用 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.
因为参数匹配器寻找字符串“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 没有意义