@RunWith(PandoraBootRunner.class)
@DelegateTo(MockitoJUnitRunner.class)
public class MetaqMessageSenderTest {
12.消除类型转换警告
在编写测试用例时,特别是泛型类型转换时,很容易产生类型转换警告。常见类型转换警告如下:
Type safety: Unchecked cast from Object to List<Object>
Type safety: Unchecked invocation forClass(Class<Map>) of the generic method forClass(Class<S>) of type ArgumentCaptor
Type safety: The expression of type ArgumentCaptor needs unchecked conversion to conform to ArgumentCaptor<Map<String,Object>>
作为一个有代码洁癖的轻微强迫症程序员,是绝对不容许这些类型转换警告产生的。于是,总结了以下方法来解决这些类型转换警告。
12.1.利用注解初始化
Mockito提供@Mock注解来模拟类实例,提供@Captor注解来初始化参数捕获器。由于这些注解实例是通过测试框架进行初始化的,所以不会产生类型转换警告。
问题代码:
Map<Long, String> resultMap = Mockito.mock(Map.class);
ArgumentCaptor<Map<String, Object>> parameterMapCaptor = ArgumentCaptor.forClass(Map.class);
建议代码:
@Mock
private Map<Long, String> resultMap;
@Captor
private ArgumentCaptor<Map<String, Object>> parameterMapCaptor;
12.2.利用临时类或接口
我们无法获取泛型类或接口的class实例,但是很容易获取具体类的class实例。这个解决方案的思路是——先定义继承泛型类的具体子类,然后mock、spy、forClass以及any出这个具体子类的实例,然后把具体子类实例转换为父类泛型实例。
问题代码:
Function<Record, Object> dataParser = Mockito.mock(Function.class);
AbstractDynamicValue<Long, Integer> dynamicValue = Mockito.spy(AbstractDynamicValue.class);
ArgumentCaptor<ActionRequest<Void>> requestCaptor = ArgumentCaptor.forClass(ActionRequest.class);
建议代码:
/** 定义临时类或接口 */
private interface DataParser extends Function<Record, Object> {};
private static abstract class AbstractTemporaryDynamicValue extends AbstractDynamicValue<Long, Integer> {};
private static class VoidActionRequest extends ActionRequest<Void> {};
/** 使用临时类或接口 */
Function<Record, Object> dataParser = Mockito.mock(DataParser.class);
AbstractDynamicValue<Long, Integer> dynamicValue = Mockito.spy(AbstractTemporaryDynamicValue.class);
ArgumentCaptor<ActionRequest<Void>> requestCaptor = ArgumentCaptor.forClass(VoidActionRequest.class);
12.3.利用CastUtils.cast方法
SpringData包中提供一个CastUtils.cast方法,可以用于类型的强制转换。这个解决方案的思路是——利用CastUtils.cast方法屏蔽类型转换警告。
问题代码:
Function<Record, Object> dataParser = Mockito.mock(Function.class);
ArgumentCaptor<ActionRequest<Void>> requestCaptor = ArgumentCaptor.forClass(ActionRequest.class);
Map<Long, Double> scoreMap = (Map<Long, Double>)method.invoke(userService);
建议代码:
Function<Record, Object> dataParser = CastUtils.cast(Mockito.mock(Function.class));
ArgumentCaptor<ActionRequest<Void>> requestCaptor = CastUtils.cast(ArgumentCaptor.forClass(ActionRequest.class));
Map<Long, Double> scoreMap = CastUtils.cast(method.invoke(userService));
这个解决方案,不需要定义注解,也不需要定义临时类或接口,能够让测试用例代码更为精简,所以作者重点推荐使用。如果不愿意引入SpringData包,也可以自己参考实现该方法,只是该方法会产生类型转换警告。
注意:CastUtils.cast方法本质是——先转换为Object类型,再强制转换对应类型,本身不会对类型进行校验。所以,CastUtils.cast方法好用,但是不要乱用,否则就是大坑(只有执行时才能发现问题)。
12.4.利用类型自动转换方法
在Mockito中,提供形式如下的方法——泛型类型只跟返回值有关,而跟输入参数无关。这样的方法,可以根据调用方法的参数类型自动转换,而无需手动强制类型转换。如果手动强制类型转换,反而会产生类型转换警告。
<T> T getArgument(int index);
public static <T> T any();
public static synchronized <T> T invokeMethod(Object instance, String methodToExecute, Object... arguments) throws Exception;
问题代码:
Mockito.doAnswer(invocation -> dataList.addAll((List<Object>)invocation.getArgument(0)))
.when(dataStorage).test(Mockito.anyList());
Mockito.doThrow(e).when(workflow).beginToPrepare((ActionRequest<Void>)Mockito.any());
Map<Long, Double> scoreMap = (Map<Long, Double>)Whitebox.invokeMethod(userService, "getScoreMap");
建议代码:
Mockito.doAnswer(invocation -> dataList.addAll(invocation.getArgument(0)))
.when(dataStorage).test(Mockito.anyList());
Mockito.doThrow(e).when(workflow).beginToPrepare(Mockito.any());
Map<Long, Double> scoreMap = Whitebox.invokeMethod(userService, "getScoreMap");
其实,SpringData的CastUtils.cast方法之所以这么强悍,也是采用了类型自动转化方法。
12.5.利用doReturn-when语句代替when-thenReturn语句
Mockito的when-thenReturn语句需要对返回类型强制校验,而doReturn-when语句不会对返回类型强制校验。利用这个特性,可以利用doReturn-when语句代替when-thenReturn语句解决类型转换警告。
问题代码:
List<String> valueList = Mockito.mock(List.class);
Mockito.when(listOperations.range(KEY, start, end)).thenReturn(valueList);
建议代码:
List<?> valueList = Mockito.mock(List.class);
Mockito.doReturn(valueList).when(listOperations).range(KEY, start, end);
12.6.利用Whitebox.invokeMethod方法代替Method.invoke方法
JDK提供的Method.invoke方法返回的是Object类型,转化为具体类型时需要强制转换,会产生类型转换警告。而PowerMock提供的Whitebox.invokeMethod方法返回类型可以自动转化,不会产生类型转换警告
问题代码:
Method method = PowerMockito.method(UserService.class, "getScoreMap");
Map<Long, Double> scoreMap = (Map<Long, Double>)method.invokeMethod(userService);
建议代码:
Map<Long, Double> scoreMap = Whitebox.invokeMethod(userService, "getScoreMap");
12.7.利用instanceof关键字
在具体类型强制转换时,建议利用instanceof关键字先判断类型,否则会产生类型转换警告。
问题代码:
JSONArray jsonArray = (JSONArray)object;
建议代码:
if (object instanceof JSONArray) {
JSONArray jsonArray = (JSONArray)object;
12.8.利用Class.cast方法
在泛型类型强制转换时,会产生类型转换警告。可以采用泛型类的cast方法转换,从而避免产生类型转换警告。
问题代码:
public static <V> V parseValue(String text, Class<V> clazz) {
if (Objects.equals(clazz, String.class)) {
return (V)text;
return JSON.parseObject(text, clazz);
建议代码:
public static <V> V parseValue(String text, Class<V> clazz) {
if (Objects.equals(clazz, String.class)) {
return clazz.cast(text);
return JSON.parseObject(text, clazz);
12.9.避免不必要的类型转换
有时候,没有必要进行类型转换,就尽量避免类型转换。比如:把Object类型转换为具体类型,但又把具体类型当Object类型使用,就没有必要进行类型转换。像这种情况,可以利用连写表达式或定义基类变量,从而避免不必要的类型转化。
问题代码:
Boolean isSupper = (Boolean)method.invokeMethod(userService, userId);
Assert.assertEquals("期望值不为真", Boolean.TRUE, isSupper);
List<UserVO> userList = (Map<Long, Double>)method.invokeMethod(userService, companyId);
Assert.assertEquals("期望值不一致", expectedJson, JSON.toJSONString(userList));
建议代码:
Assert.assertEquals("期望值不为真", Boolean.TRUE, method.invokeMethod(userService, userId));
Object userList = method.invokeMethod(userService, companyId);
Assert.assertEquals("期望值不一致", expectedJson, JSON.toJSONString(userList));
登妙峰山记
山高路远车难骑,
精疲力尽人易弃。
多少妙峰登顶者,
又练心境又练力!
骑行的人,一定要沉得住气、要吃得了苦、要耐得住寂寞、要意志坚定不移、要体力够猛够持久……恰好,这也正是技术人所要具备的精神。只要技术人做到了这些,练就了好的“心境”和“体力”,才有可能登上技术的“妙峰山”。
有时,不仅要验证对象的方法是否被调用,还要检查传给方法的参数是否正确。
今天,埃德蒙的工作是给类DownloadActive写ut。
DownloadActive的功能是下载软件,并将成功与否的结果通知Listener.
doTask首先做下载的动作,然后发通知。
发通知,即调用listener的report方法。
为了方便说明,将doTask简化,只是通知侦听者们一个字符串“OK”。...
1、校验方法是否调用
Mockito提供vertify关键字来实现校验方法是否被调用,具体调用如下例子:@Test
public void update() throws Exception {
boolean result = personService.update(1, "new name");
//验证mockDao的getPer是否被调用
Mockito 是一个针对 Java 的单元测试模拟框架,可以简化单元测试过程中测试上下文对象。它可以做如下事情:
1)模拟方法的返回值、模拟抛出异常
2)验证方法被调用次数、验证方法参数类型
3)捕获方法参数值
4)为真实对象创建一个监控(spy)对象
1)不能 Mock 静态方法
2)不能 Mock private 方法
3)不能 Mock final class
整理Mockito结合ArgumentCaptor捕获多次调用的方法的参数
今天在对业务代码进行开发时,发现需要捕获多次调用的方法的参数,验证参数传递的正确性,这里对获取方法进行总结。
首先,模拟一个简单的测试场景,有一个对外提供服务的类:
@Component
public class CaptorLearning {
private DalService dalService;
@Autowired
public CaptorLearning(DalService dal.
Mockito框架中的参数匹配器是用于在测试中进行灵活验证和存根设置的工具。如果使用了参数匹配器,方法中的所有参数都必须是匹配器。
参数匹配器列表:示例:使用anyInt()方法来指定参数的范围,从而实现了对mockList.get()方法的灵活验证和存根设置。
②any(Class type)
示例:使用参数匹配器any(Class type)来存根方法
③eq()
示例:使用参数匹配器eq(value)来存根方法
④same(expectedObject)
⑤endsWith()
特殊的匹配器,