问题1:@Mock和@InjectMocks的区别
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface Mock {
Answers answer() default Answers.RETURNS_DEFAULTS;
String name() default "";
Class<?>[] extraInterfaces() default {};
boolean serializable() default false;
}
可以看出@Mock用于类的属性注解,且运行时才能发挥作用。
@Documented
@Target(FIELD)
@Retention(RUNTIME)
public @interface InjectMocks {}
@InjectMocks用于类的属性注解,且运行时发挥作用。
Mark a field as a mock.
@Mock注解的属性对象,在代码运行时,调用属性的方法不会进入具体的方法体;
Mark a field on which injection should be performed.
@InjectMocks注解的属性对象,在代码运行时,调用该属性的方法,则会进入具体的方法体。
问题2:@BeforeMethod和@BeforeClass的区别
@BeforeMethod注解源码
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(java.lang.annotation.ElementType.METHOD)
public @interface BeforeMethod {
public boolean enabled() default true;
public String[] groups() default {};
public String[] dependsOnGroups() default {};
public String[] dependsOnMethods() default {};
public boolean alwaysRun() default false;
public boolean inheritGroups() default true;
public String description() default "";
public boolean firstTimeOnly() default false; // 注意这个方法
public long timeOut() default 0;
}
@BeforeClass注解源码
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(java.lang.annotation.ElementType.METHOD)
public @interface BeforeClass {
public boolean enabled() default true;
public String[] groups() default {};
public String[] dependsOnGroups() default {};
public String[] dependsOnMethods() default {};
public boolean alwaysRun() default false;
public boolean inheritGroups() default true;
public String description() default "";
public long timeOut() default 0;
}
源码中很明显表达出:
- @BeforeMethod注解和@BeforeClass注解用于方法上;
- @BeforeMethod注解的方法可以多次执行,而@BeforeClass注解的方法没有明确说明;
- @BeforeClass注解的方法在测试类中只会执行一次,@BeforeMethod注解的方法在测试类中每个方法执行前都会执行一次;
那么它们分别在什么情况下使用?
我个人觉得,如果每个方法都需要初始化或者注入一些对象,而这些对象在其他方法中已经被改变,则应该使用@BeforeMethod,否则应该使用@BeforeClass。
问题3:如何Mock静态方法?
最近在写测试类,发现对于静态方法的mock不能按照对象方法的思路来测试。先说说如何做的吧。
第一步:测试类继承PowerMockTestCase类;
第二步:在测试类上添加注解:@PrepareForTest({xxUtils.clss});
注意:xxUtils.class为静态方法所在的类,如果有多个类,可用逗号隔开。
第三步:在初始化方法内添加PowerMockito.mockStatic(xxUtils.class);
第四步:测试方法内通过PowerMockito.when(xxUtils.method(args…)).then…模拟静态方法;
实例:
定义一个工具类,并含有一个检查范围的静态类:
public class TestUtils {
public static boolean checkScope(int value, int minValue, int maxValue) throws LSQException {
if (minValue > maxValue) {
throw new LSQException("最小值不应大于最大值.");
}
if (value < minValue || value > maxValue){
return false;
}
return true;
}
}
实体类Client调用静态方法:
public class Client {
public void method1() {
boolean result = false;
try {
result = TestUtils.checkScope(3, 0, 2); // 运行结果为false, 但测试结果返回的是true,表明测试通过
} catch (LSQException e) {
e.printStackTrace();
}
System.out.println(result);
}
}
那么测试类如下:
@PrepareForTest({TestUtils.class})
public class TestUtilsTest extends PowerMockTestCase {
@InjectMocks
Client client;
@BeforeClass
public void BeforeClass(){
MockitoAnnotations.initMocks(this);
PowerMockito.mockStatic(TestUtils.class);
}
@Test
public void test0(){
try {
PowerMockito.when(TestUtils.checkScope(Matchers.anyInt(), Matchers.anyInt(), Matchers.anyInt())).thenReturn(true);
} catch (LSQException e) {
e.printStackTrace();
}
client.method1();
}
@Test
public void test1(){
try {
PowerMockito.when(TestUtils.checkScope(Matchers.anyInt(), Matchers.anyInt(), Matchers.anyInt())).thenThrow(new LSQException("test"));
} catch (LSQException e) {
e.printStackTrace();
}
client.method1();
}
}
测试结果为:test0通过,test1不通过。
报错为:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Misplaced argument matcher detected here:
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
verify(mock).someMethod(contains("foo"))
Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.
上面错误大致意思是:检测到参数匹配器有误。。百度好久都没有找到具体原因。。。几乎崩溃。。。
但能锁定的是PowerMockito的问题,单独运行test1能通过,同时运行却不行,因此我直觉性的把@BeforeClass改为@BeforeMethod,再次运行,顺利通过。
什么原因呢?应该是要为每个测试方法都模拟出一个静态方法的类,或者说不同方法中,每调用一次静态方法就需要模拟出一个静态方法的类。为了验证我的想法,我把两个测试方法合并为一个方法,测试通过,因此上述说法是成立哒。
原理分析:
PowerMockTestCase类专门用来模拟静态方法,它和注解@PrepareForTest配合使用;模拟出静态方法的调用。关于PowerMockTestCase可以看这篇文章。
问题4:如何处理一个对象内A方法与B方法互调的场景?
关于这个问题,很常见,因为一个类不全是外部对象的方法。
最先的思路是:再@Mock出一个模拟对象,在测试方法中使用该模拟对象来调用A方法中的B方法,而不是使用@InjectMock模拟出的对象。本以为这样可以绕过B方法,而避免资源消耗;但结果并不像预期那样。
分析原因:虽然@Mock出一个对象,但该对象与@InjectMock对象不同名,在测试方法中通过@InjectMock对象调用A方法,所以调用B方法同样是@InjectMock对象,而并非@Mock对象,所以结果很显然。
想了好久没有找到可行的方案,若有哪位大佬有更好的办法,麻烦告知,谢谢~
问题5:如何Mock类中的私有方法?
私有方法无法通过正常的InjectMock类来调用进行测试。下面提供一个实例来解决私有方法的测试:
普通类:
public class A {
private void method(String str) {
System.out.println(str);
}
}
测试类:
public class ATest {
@InjectMocks
private A a;
@BeforeMethod
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void method() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//a.method(""); 报错
// 参数1是要调用的方法名, 参数2是方法参数类型,可以有多个参数
Method method = a.getClass().getDeclaredMethod("method", String.class);
method.setAccessible(true); // 放开权限
// 调用方法,参数1是实体bean,参数2是该方法的具体参数
method.invoke(a, new Object[] {"haha"});
// 关闭方法访问权限
method.setAccessible(false);
}
}
可以看出,这种访问方式是通过反射修改方法的访问权限来实现的。