Spring与Mockito组合单元测试简单实用

今天有点空了,把单元测试的那点事整理了一下。个人觉得Spring应用和Mockito结合做单元测试简单实用,分享出来供参考。
这里不啰嗦单元测试的重要性...。很多应用是基于Spring,而Mockito简单易用易上手,所以就把Spring和Mockito组合做单元测试,Mocked对象也交给Spring统一管理。好处至少有:使单元测试类的环境和应用实际的环境保持一致性。开发人员就不用另外增加额外的配置,也可以少一些代码。单元测试类通过了,相应的应用类也就ok(Spring的相关配置也ok)。

1,为了把Mock对象也纳入Spring。需继承DependencyInjectionTestExecutionListener并增加Mock对象和注入依赖对象为Mock对象。
代码:
public class MockitoDependencyInjectionTestExecutionListener extends DependencyInjectionTestExecutionListener {
private static final Map<String, MockObject> mockObject = new HashMap<String, MockObject>();
private static final List<Field> injectFields = new ArrayList<Field>();
@Override
protected void injectDependencies(final TestContext testContext) throws Exception {
super.injectDependencies(testContext);
init(testContext);
}
protected void injectMock(final TestContext testContext) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext().getAutowireCapableBeanFactory();
for (Field field : injectFields) {
Object o = beanFactory.getBean(field.getName(), field.getType());
if (null != o) {
Method[] methods = o.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().startsWith("set")) {
for (Iterator it = mockObject.keySet().iterator(); it.hasNext();) {
String key = (String) it.next();
if (method.getName().equalsIgnoreCase("set" + key)) {
method.invoke(o, mockObject.get(key).getObj());
break;
}
}
}
}
}
}
}

private void init(final TestContext testContext) throws Exception {
Object bean = testContext.getTestInstance();
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
for (Annotation antt : annotations) {
if (antt instanceof org.mockito.Mock) {
// 注入mock实例
MockObject obj = new MockObject();
obj.setType(field.getType());
obj.setObj(mock(field.getType()));
field.setAccessible(true);
field.set(bean, obj.getObj());
mockObject.put(field.getName(), obj);
} else if (antt instanceof Autowired) {
// 只对autowire重新注入
injectFields.add(field);
}
}
}
for (Field field : injectFields) {
field.setAccessible(true);
Object object = field.get(bean);
if (object instanceof Proxy) {
// 如果是代理的话,找到真正的对象
Class targetClass = AopUtils.getTargetClass(object);
if (targetClass == null) {
// 可能是远程实现
return;
}
Field[] targetFields = targetClass.getDeclaredFields();
for (int i = 0; i < targetFields.length; i++) {
// 针对每个需要重新注入的字段
for (Map.Entry<String, MockObject> entry : mockObject.entrySet()) {
// 针对每个mock的字段
if (targetFields[i].getName().equals(entry.getKey())) {
targetFields[i].setAccessible(true);
targetFields[i].set(getTargetObject(object, entry.getValue().getType()),
entry.getValue().getObj());
}
}
}
} else {
injectMock(testContext);
}
}
}

protected <T> T getTargetObject(Object proxy, Class<T> targetClass) throws Exception {
if (AopUtils.isJdkDynamicProxy(proxy)) {
return (T) ((Advised) proxy).getTargetSource().getTarget();
} else {
return (T) proxy; // expected to be cglib proxy then, which is simply a specialized class
}
}

public static class MockObject {
private Object obj;
private Class<?> type;

public MockObject(){
}

public Object getObj() {
return obj;
}

public void setObj(Object obj) {
this.obj = obj;
}

public Class<?> getType() {
return type;
}

public void setType(Class<?> type) {
this.type = type;
}
}

public static Map<String, MockObject> getMockobject() {
return mockObject;
}

public static List<Field> getInjectfields() {
return injectFields;
}
}



2,单元测试继承AbstractJUnit4SpringContextTests类。这里用一个抽象类把必须的注解上。
代码:
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ MockitoDependencyInjectionTestExecutionListener.class })
public abstract class BaseTestCase extends AbstractJUnit4SpringContextTests {

}


3,测试用例 demo。注意在MockitoDependencyInjectionTestExecutionListener中已经mock对象了,不要再用MockitoAnnotations.initMocks(this)。
代码:
@ContextConfiguration(locations = { "/applicationContext.xml" })
public class DemoServiceTest extends BaseTestCase {

@Autowired
private DemoService demoService;

@Mock
private DemoDao demoDao;

@Test
public void testGetResults() {
List<Model> list = new ArrayList<Model>();
Model model = new Model("id", "name");
list.add(model);

// 先设置预期
when(demoDao.getModel("id")).thenReturn(model);
// when(demoDao.getResults()).thenAnswer(new Answer<List<Model>>() {
//
// @Override
// public List<Model> answer(InvocationOnMock invocation) throws Throwable {
// List<Model> list = new ArrayList<Model>();
// Model model = new Model("id", "name");
// list.add(model);
// return list;
// }
//
// });

Model m = demoService.getModel("id");
assertSame(m, model);
// List<Model> result = demoService.getResults();
// assertTrue(result.size() == 1);
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值