mockito,当使用 when/thenReturn 设置模拟存根时出现异常
mockito, when setup mock stub using when/thenReturn it got exception
有 class 并且想要 mock/stub 一种方法
public class ToBeMocked {
public List<HttpCookie> mergeList(@NonNull List<HttpCookie> cookies, HttpCookie oneCookie) {
System.out.println("+++ mergeList(), cookies:"+cookies+", oneCookie:"+oneCookie);
HashMap<String, HttpCookie> map = new HashMap();
if (oneCookie != null) {
map.put("A", oneCookie);
}
for (HttpCookie cookie : cookies) { //<=== it crashed at this line
map.put(cookie.getName(), cookie);
}
List<HttpCookie> list = new ArrayList<HttpCookie>();
for (Map.Entry<String, HttpCookie> entry : map.entrySet()) {
list.add(entry.getValue());
}
return list;
}
}
测试;
@Test
public void test() {
List<HttpCookie> aCookieList = new ArrayList<>();
HttpCookie a1Cookie = new HttpCookie("A1", "a1");
HttpCookie a2Cookie = new HttpCookie("A2", "a2");
aCookieList.add(a1Cookie);
aCookieList.add(a2Cookie);
HttpCookie bCookie = new HttpCookie("B", "b1");
List<HttpCookie> fakeCookieList = new ArrayList<>();
fakeCookieList.add(bCookie);
fakeCookieList.addAll(aCookieList);
ToBeMocked theSpy = spy(new ToBeMocked());
System.out.println("+++ 111 test(), aCookieList:"+aCookieList+", bCookie:"+bCookie);
//when(theSpy.mergeList(any(List.class), any(HttpCookie.class)))
when(theSpy.mergeList(eq(aCookieList), any(HttpCookie.class))). //<== exception on this
.thenReturn(fakeCookieList);
System.out.println("+++ 222 test()");
// test
// it would call some other function which internally call the mergeList(aCookieList, bCookie), and expect to generate a list from the stubbed result to use, here just make it simple to be run able to show the problem
List<HttpCookie> list = theSpy.mergeList(aCookieList, bCookie);
// verify
assertEquals(list.contains(bCookie), true);
}
在 when(theSpy.mergeList(any(List.class), any(HttpCookie.class))).thenReturn(fakeCookieList);
上得到异常 NullPointerException
。
日志显示两行:
Called loadFromPath(/system/framework/framework-res.apk, true); mode=binary sdk=28
+++ 111 test(), aCookieList:[A1="a1", A2="a2"], bCookie:B="b1"
+++ mergeList(), cookies:null, oneCookie:null
java.lang.NullPointerException
显然 mergeList() 使用空参数执行,并在
for (HttpCookie cookie : cookies)
问题:
认为 when().thenReturn() 仅用于设置存根,这表示当使用任何参数(或特定参数)调用模拟的 mergeList() 时,它应该 return提供的列表。
when().thenReturn() 的参数不正确吗?
为什么它似乎在 when().thenReturn() 中执行 mergeList()?
来自 Mockito doc:
Sometimes it's impossible or impractical to use when(Object) for
stubbing spies. Therefore when using spies please consider
doReturn|Answer|Throw() family of methods for stubbing.
所以试试看:
doReturn(fakeCookieList).when(theSpy)
.mergeList(eq(aCookieList), any(HttpCookie.class));
但总的来说,你不太清楚你在用这样的测试测试什么。被测方法是 mergeList
,同时你通过调用 doReturn
指定它的行为(return 值),最后只检查 returned 值。 (当然,如果真实代码完全表示的话)
@George Lvov 的解决方案有效。
Here 解释了 doReturn(...) 和 theReturn(...) 之间的区别,特别是在间谍中 doReturn(...)
不会进行真正的方法调用。
但是还没有找到记录行为差异的地方。
如果您使用间谍对象(用@Spy 注释)而不是模拟对象(用@Mock 注释),两种方法的行为会有所不同:
when(...) thenReturn(...) 在返回指定值之前进行真正的方法调用。因此,如果被调用的方法抛出异常,您必须处理它/模拟它等。当然您仍然会得到结果(您在 thenReturn(...) 中定义的内容)
doReturn(...) when(...) 根本不调用该方法。
有 class 并且想要 mock/stub 一种方法
public class ToBeMocked {
public List<HttpCookie> mergeList(@NonNull List<HttpCookie> cookies, HttpCookie oneCookie) {
System.out.println("+++ mergeList(), cookies:"+cookies+", oneCookie:"+oneCookie);
HashMap<String, HttpCookie> map = new HashMap();
if (oneCookie != null) {
map.put("A", oneCookie);
}
for (HttpCookie cookie : cookies) { //<=== it crashed at this line
map.put(cookie.getName(), cookie);
}
List<HttpCookie> list = new ArrayList<HttpCookie>();
for (Map.Entry<String, HttpCookie> entry : map.entrySet()) {
list.add(entry.getValue());
}
return list;
}
}
测试;
@Test
public void test() {
List<HttpCookie> aCookieList = new ArrayList<>();
HttpCookie a1Cookie = new HttpCookie("A1", "a1");
HttpCookie a2Cookie = new HttpCookie("A2", "a2");
aCookieList.add(a1Cookie);
aCookieList.add(a2Cookie);
HttpCookie bCookie = new HttpCookie("B", "b1");
List<HttpCookie> fakeCookieList = new ArrayList<>();
fakeCookieList.add(bCookie);
fakeCookieList.addAll(aCookieList);
ToBeMocked theSpy = spy(new ToBeMocked());
System.out.println("+++ 111 test(), aCookieList:"+aCookieList+", bCookie:"+bCookie);
//when(theSpy.mergeList(any(List.class), any(HttpCookie.class)))
when(theSpy.mergeList(eq(aCookieList), any(HttpCookie.class))). //<== exception on this
.thenReturn(fakeCookieList);
System.out.println("+++ 222 test()");
// test
// it would call some other function which internally call the mergeList(aCookieList, bCookie), and expect to generate a list from the stubbed result to use, here just make it simple to be run able to show the problem
List<HttpCookie> list = theSpy.mergeList(aCookieList, bCookie);
// verify
assertEquals(list.contains(bCookie), true);
}
在 when(theSpy.mergeList(any(List.class), any(HttpCookie.class))).thenReturn(fakeCookieList);
上得到异常 NullPointerException
。
日志显示两行:
Called loadFromPath(/system/framework/framework-res.apk, true); mode=binary sdk=28
+++ 111 test(), aCookieList:[A1="a1", A2="a2"], bCookie:B="b1"
+++ mergeList(), cookies:null, oneCookie:null
java.lang.NullPointerException
显然 mergeList() 使用空参数执行,并在
for (HttpCookie cookie : cookies)
问题:
认为 when().thenReturn() 仅用于设置存根,这表示当使用任何参数(或特定参数)调用模拟的 mergeList() 时,它应该 return提供的列表。
when().thenReturn() 的参数不正确吗? 为什么它似乎在 when().thenReturn() 中执行 mergeList()?
来自 Mockito doc:
Sometimes it's impossible or impractical to use when(Object) for stubbing spies. Therefore when using spies please consider doReturn|Answer|Throw() family of methods for stubbing.
所以试试看:
doReturn(fakeCookieList).when(theSpy)
.mergeList(eq(aCookieList), any(HttpCookie.class));
但总的来说,你不太清楚你在用这样的测试测试什么。被测方法是 mergeList
,同时你通过调用 doReturn
指定它的行为(return 值),最后只检查 returned 值。 (当然,如果真实代码完全表示的话)
@George Lvov 的解决方案有效。
Here 解释了 doReturn(...) 和 theReturn(...) 之间的区别,特别是在间谍中 doReturn(...)
不会进行真正的方法调用。
但是还没有找到记录行为差异的地方。
如果您使用间谍对象(用@Spy 注释)而不是模拟对象(用@Mock 注释),两种方法的行为会有所不同: when(...) thenReturn(...) 在返回指定值之前进行真正的方法调用。因此,如果被调用的方法抛出异常,您必须处理它/模拟它等。当然您仍然会得到结果(您在 thenReturn(...) 中定义的内容) doReturn(...) when(...) 根本不调用该方法。