介绍Mock interface的几种方法
如果使用了接口,例如使用了 Service,那么Mock是个头疼的事情,一般有三种方法,一种是直接实现接口类,只实现相关类方法,其他的存根(stub)方法,直接返为空或者返回null即可。下面分别讲述三种方法的优缺点。
本文基于 JMock 编写,需要在 pom.xml 中,添加依赖:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
假设有以下接口和实现类,以及调用接口的使用类:
Abc.java 接口
public interface Abc {
int method1();
int method2();
int method3();
}
AbcImpl.java 实现类
package tacos.util.Impl;
import org.springframework.stereotype.Component;
import tacos.util.Abc;
@Component
public class AbcImpl implements Abc {
@Override
public int method1() {
return 50;
}
@Override
public int method2() {
return 0;
}
@Override
public int method3() {
return 0;
}
}
Dummy.java 调用接口的实现类
package tacos.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Dummy {
@Autowired
private Abc abc;
public int callMe() {
return abc.method1();
}
}
我们要写单测的目标单元为 Dummy.java,下面分别讲述三种 Mock Abc 接口的方法。
使用TestNG/JMock,可以直接Mock接口。例如:
@Test
public void testMockInterface() throws NoSuchFieldException, IllegalAccessException, InstantiationException {
MockUp<Abc> mock = new MockUp<Abc>(){
@Mock
public int method1() {
return 100;
}
};
Field field = dummy.getClass().getDeclaredField("abc");
field.setAccessible(true);
field.set(dummy, mock.getMockInstance());
assertEquals(dummy.callMe(), 100);
}
首先,在测试类开头,声明需要Mock的接口变量:
@Injectable
接口类 变量名;
然后在代码中,注入接口变量:
Field field = dummy.getClass().getDeclaredField("变量名");
field.setAccessible(true);
field.set(dummy, abc);
最后写上 new Expectation代码即可,即当调用对应接口的某个方法时,返回什么东西,其中方法名,可以使用参数匹配器,Any代表任何参数都返回Mock的数据。
new Expectations() {{
接口.方法名(Any);
result = 需要mock返回的东西;
}};
注意,Expectation,需要每调用一次,都需要写对应的代码,如果某个方法调用了10次该接口的方法,你需要写10个new Expectations!
示例代码:
@Injectable
Abc abc;
@Test
public void testExpectation() throws NoSuchFieldException, IllegalAccessException {
Field field = dummy.getClass().getDeclaredField("abc");
field.setAccessible(true);
field.set(dummy, abc);
new Expectations() {{
abc.method1();
result = 100;
}};
assertEquals(dummy.callMe(), 100);
}
不推荐使用本方法,因为任何接口改动,需要同步改动代码,并且如果添加或者减少接口的方法定义,需要同步修改代码,在不断变动的情况下,非常讨厌,容易导致编译失败和代码合并冲突。
DummyTest.java 单元测试
package tacos.util;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import tacos.TacoCloudApplication;
import java.lang.reflect.Field;
import static org.testng.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TacoCloudApplication.class)
public class DummyTest {
@Autowired
Dummy dummy;
@Test
public void testMethod1() throws NoSuchFieldException, IllegalAccessException {
Abc abc = new Abc() {
@Override
public int method1() {
return 100;
}
@Override
public int method2() {
return 0;
}
@Override
public int method3() {
return 0;
}
};
Field field = dummy.getClass().getDeclaredField("abc");
field.setAccessible(true);
field.set(dummy, abc);
assertEquals(dummy.callMe(), 100);
}
}
MockUp Repository