单元测试

本文详细介绍了Mockito框架的使用,包括参数匹配器、验证执行顺序、使用Spy监控真实对象以及@InjectMocks自动注入依赖对象。此外,还探讨了PowerMockito框架,用于扩展Mockito的功能,如模拟静态方法、final方法、私有方法和静态类,解决了Mockito无法处理的一些场景。通过实例展示了PowerMockito在不同情况下的用法,如Mock方法内部new出来的对象、Mock静态方法、Mock私有方法和Mock系统的final静态类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Stubbing:

Stub对象用来提供测试时所需要的测试数据,对各种交互设置相应的回应。Mockito使用when(...).thenReturn(...)设置方法调用的返回值,使用when(...).thenThrow(...)设置方法调用时抛出的异常。

doReturn(Object toBeReturned)提前设置要返回的值

doThrow(Throwable… toBeThrown)提前设置要抛出的异常

doAnswer(Answer answer)提前对结果进行拦截

doCallRealMethod()调用某一个方法的真实实现

doNothing()设置void函数什么也不做

thenReturn(T value)设置要返回的值

thenThrow(Throwable… throwables)设置要抛出的异常

thenAnswer(Answer<?> answer)对结果进行拦截
 

    @Test
    public void testMock2() {
        //不仅可以针对接口mock, 还可以针对具体类
        LinkedList list = mock(LinkedList.class);

        //设置返回值,当调用list.get(0)时会返回"first"
        when(list.get(0)).thenReturn("first");
        //当调用list.get(1)时会抛出异常
        when(list.get(1)).thenThrow(new RuntimeException());

        //会打印"print"
        System.out.println(list.get(0));
        //会抛出RuntimeException
        System.out.println(list.get(1));
        //会打印 null
        System.out.println(list.get(99));

        verify(list).get(0);
    }
  • 对于有返回值的方法,mock会默认返回null、空集合、默认值。比如为int/Integer返回0,为boolean/Boolean返回false、为Object返回null。
  • 一旦stubbing,不管方法被调用多少次,都永远返回stubbing的值。
  • stubbing可以被覆盖, 如果对同一个方法进行多次stubbing,最后一次的stubbing会生效

参数匹配器 (matchers)

anyObject()匹配任何对象

any(Class type)与anyObject()一样

anyBoolean()匹配任何boolean和非空Booleanany

Byte()匹配任何byte和非空Byteany

Collection()匹配任何非空Collectionany

Double()匹配任何double和非空Double

anyFloat()匹配任何float和非空Floatany

Int()匹配任何int和非空Integerany

List()匹配任何非空List

anyLong()匹配任何long和非空Long

anyMap()匹配任何非空Map

anyString()匹配任何非空String

contains(String substring)参数包含给定的substring字符串

argThat(ArgumentMatcher matcher)创建自定义的参数匹配模式

eq(T value)匹配参数等于某个值

@Test
    public void testPersonAny(){
        when(mPerson.eat(any(String.class))).thenReturn("米饭");
        //或:
        when(mPerson.eat(anyString())).thenReturn("米饭");
        //输出米饭
        System.out.println(mPerson.eat("面条"));
    }

    @Test
    public void testPersonContains(){
        when(mPerson.eat(contains("面"))).thenReturn("面条");
        //输出面条
        System.out.println(mPerson.eat("面"));
    }

    @Test
    public void testPersonArgThat(){
        //自定义输入字符长度为偶数时,输出面条。
        when(mPerson.eat(argThat(new ArgumentMatcher<String>() {
            @Override
            public boolean matches(String argument) {
                return argument.length() % 2 == 0;
            }
        }))).thenReturn("面条");
        //输出面条
        System.out.println(mPerson.eat("1234"));
    }

需要注意的是,如果你打算使用参数匹配器,那么所有参数都必须由匹配器提供。例如:

 

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代码是正确的,因为eq()也是一个参数匹配器

verify(mock).someMethod(anyInt(), anyString(), "third argument");
// 上述代码是错误的, 因为所有参数必须由匹配器提供,而参数"third argument"并非由参数匹配器提供,因此会抛出异常

像anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。

使用InOrder验证执行执行顺序

验证执行执行顺序主要使用InOrder函数
如,验证mock一个对象的函数执行顺序:

 

    @Test
    public void testInorder() {
        List<String> singleMock = mock(List.class);

        singleMock.add("小明");
        singleMock.add("小红");

        // 为该mock对象创建一个inOrder对象
        InOrder inOrder = inOrder(singleMock);

        // 验证add函数首先执行的是add("小明"),然后才是add("小红"),否则测试失败
        inOrder.verify(singleMock).add("小明");
        inOrder.verify(singleMock).add("小红");
    }

验证多个mock对象的函数执行顺序:

 

    @Test
    public void testInorderMulti() {
        List<String> firstMock = mock(List.class);
        List<String> secondMock = mock(List.class);

        firstMock.add("小明");
        secondMock.add("小红");

        // 为这两个Mock对象创建inOrder对象
        InOrder inOrder = inOrder(firstMock, secondMock);

        // 验证它们的执行顺序
        inOrder.verify(firstMock).add("小明");
        inOrder.verify(secondMock).add("小红");
    }

验证执行顺序是非常灵活的,你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可。 你可以选择单个mock对象和多个mock对象混合着来,也可以仅通过那些需要验证顺序的mock对象来创建InOrder对象。

使用Spy监控真实对象

监控真实对象使用spy()函数生成,或者也可以像@Mock那样使用@Spy注解来生成一个监控对象, 当你你为真实对象创建一个监控(spy)对象后,在你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了。尽量少使用spy对象,使用时也需要小心形式。

    @Test
    public void testSpy() {
        List<String> list = new ArrayList<>();
        List<String> spy = spy(list);

        // 你可以选择为某些函数打桩
        when(spy.size()).thenReturn(100);

        // 调用真实对象的函数
        spy.add("one");
        spy.add("two");

        // 输出第一个元素"one"
        System.out.println(spy.get(0));

        // 因为size()函数被打桩了,因此这里返回的是100
        System.out.println(spy.size());

        // 验证交互
        verify(spy).add("one");
        verify(spy).add("two");
    }

使用@Spy生成监控对象:

 

    @Spy
    Person mSpyPerson;

    @Test
    public void testSpyPerson() {
        //将会输出Person 类中getName()的真实实现,而不是null
        System.out.println(mSpyPerson.getName());
    }

理解监控真实对象非常重要!有时,在监控对象上使用when(Object)来进行打桩是不可能或者不切实际的。因此,当使用监控对象时请考虑doReturn|Answer|Throw()函数族来进行打桩。例如:

 

List list = new LinkedList();
List spy = spy(list);

// 不可能实现 : 因为当调用spy.get(0)时会调用真实对象的get(0)函数,
// 此时会发生IndexOutOfBoundsException异常,因为真实List对象是空的
 when(spy.get(0)).thenReturn("foo");

// 你需要使用doReturn()来打桩
doReturn("foo").when(spy).get(0);

使用@InjectMocks自动注入依赖对象

 

有时我们要测试的对象内部需要依赖另一个对象,例如:

 

public class User {
    private Address address;

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getAddress() {
        return address.getDetail();
    }
}

 

public class Address {
    public String getDetail() {
        return "detail Address";
    }
}

User类内部需要依赖Address类,当我们测试的时候需要mock出这两个对象,然后将Address对象传入到User当中,这样如果依赖的对象多了的话就相当麻烦,Mockito 提供了可以不用去手动注入对象的方法,首先使用@InjectMocks注解需要被注入的对象,如User,然后需要被依赖注入的对象使用@Mock或@Spy注解,之后Mockito 会自动完成注入过程,例如:

 

    @InjectMocks
    User mTestUser;
    @Mock
    Address mAddress;
    @Test
    public void argumentInjectMock() {
        when(mAddress.getDetail()).thenReturn("浙江杭州");
        System.out.println(mTestUser.getAddress());
    }

这样就不用关心为User 设置Address ,只要为User需要依赖的类添加注解就可以了,然后直接将重点放到测试方法的编写上。

或者使用@Spy监控真实对象注入也可以:

 

    @InjectMocks
    User mTestUser;
    @Spy
    Address mAddress;
    @Test
    public void argumentInjectMock() {
        //  when(mAddress.getDetail()).thenReturn("浙江杭州");
        System.out.println(mTestUser.getAddress());
    }

PowerMockito框架使用

Mockito框架基本满足需求但是有一些局限性,如对static、final、private等方法不能mock,PowerMockito就可以解决这些问题,PowerMockito是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。

当所测逻辑里有静态工具类方法或私有方法我们希望他返回特定值时(极值边界、异常测试场景),我们要用到PowerMock去弥补Mockito的不足,除此之外,用Mockito去写单测能完成我们日常任务95%的场景。

Mockito框架基本满足需求但是有一些局限性,如对static、final、private等方法不能mock,PowerMockito就可以解决这些问题,PowerMockito是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。

添加依赖:

 

    testImplementation 'org.powermock:powermock-module-junit4:2.0.2'
    testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.2'
    testImplementation 'org.powermock:powermock-api-mockito2:2.0.2'
    testImplementation 'org.powermock:powermock-classloading-xstream:2.0.2'

1. 普通Mock的方式

目标类:

 

public class CommonExample {
    public boolean callArgumentInstance(File file) {
        return file.exists();
    }
}

测试类:

 

public class CommonExamplePowerMockTest {
    @Test
    public void testCallArgumentInstance() {
        File file = PowerMockito.mock(File.class);
        CommonExample commonExample = new CommonExample();
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(commonExample.callArgumentInstance(file));
    }
 }

普通Mock方式是外部传递Mock参数,基本上和单独使用Mockito是一样的,使用纯Mockito的api也可以完成这个测试。

2. Mock方法内部new出来的对象

 

public class CommonExample {
    public boolean callArgumentInstance(String path) {
        File file = new File(path);
        return file.exists();
    }
}

 

@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class CommonExamplePowerMockTest {
    @Test
    public void callCallArgumentInstance2() throws Exception {
        File file = PowerMockito.mock(File.class);
        CommonExample commonExample = new CommonExample();
        PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file);
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(commonExample.callArgumentInstance("aaa"));
    }
}

跟前面有一点区别的就是,这里要测试的方法内部创建了File对象,这时需要通过PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file)方法模拟创建File的操作,当File类以aaa的参数创建的时候返回已经mock出来的file对象。同时这时需要在测试类上添加注解@RunWith(PowerMockRunner.class)和@PrepareForTest(CommonExample.class),注意是在类上面添加,不是在方法上,一开始在方法上添加时提示找不到测试方法,@PrepareForTest()括号里面指定的是要测试的目标类。

3. Mock普通对象的final方法

 

public class CommonExample {
    public boolean callFinalMethod(DependencyClass dependency) {
        return dependency.isValidate();
    }
}

public class DependencyClass {
    public final boolean isValidate() {
        // do something
        return false;
    }
}

 

@RunWith(PowerMockRunner.class)
@PrepareForTest({CommonExample.class, DependencyClass.class})
public class CommonExamplePowerMockTest {
    @Test
    public void callFinalMethod() {
        DependencyClass dependency = PowerMockito.mock(DependencyClass.class);
        CommonExample commonExample = new CommonExample();
        PowerMockito.when(dependency.isValidate()).thenReturn(true);
        Assert.assertTrue(commonExample.callFinalMethod(dependency));
    }
}

同样这里mock出来需要依赖的类的对象,然后传递给调用方法,这里同样需要添加@RunWith和@PrepareForTest,@PrepareForTest可以指定多个目标类,但是这里如果你只需要测试final的话,只添加DependencyClass.class一个就可以了。

4. Mock普通类的静态方法

 

public final class Utils {
    public static String getUUId() {
        return UUID.randomUUID().toString();
    }
}

public class CommonExample {
    public String printUUID() {
        return Utils.getUUId();
    }
}

 

@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)
public class StaticUnitTest {

    @Before
    public void setUp() throws Exception {
        PowerMockito.mockStatic(Utils.class);
    }

    @Test
    public void getUUId() {
        PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
        CommonExample commonExample = new CommonExample();
        assertThat(commonExample.printUUID(), is("FAKE UUID"));
    }
}

同样需要指定@RunWith和@PrepareForTest,@PrepareForTest中指定静态方法所在的类,测试静态方法之前需要调用PowerMockito.mockStatic()方法来mock静态类,然后就通过when().thenReturn()方法指定静态方法的模拟返回值即可。

5. verify静态方法的调用次数

 

@Test
    public void testVerify() {
        PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
        CommonExample commonExample = new CommonExample();
        System.out.println(commonExample.printUUID());
        PowerMockito.verifyStatic(Utils.class);
        Utils.getUUId();
    }

静态方法通过PowerMockito.verifyStatic(Class c)进行验证,不过这里跟Mocktio有一点区别的是需要在这个方法的后面再调用一次静态方法,否则不行。这里PowerMockito.verifyStatic(Utils.class)其实等同于PowerMockito.verifyStatic(Utils.class, times(1)),如果想验证超过一次的,那么:

 

    @Test
    public void testVerify() {
        PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
        CommonExample commonExample = new CommonExample();
        System.out.println(commonExample.printUUID());
        System.out.println(commonExample.printUUID());
        PowerMockito.verifyStatic(Utils.class, Mockito.times(2));
        Utils.getUUId();
    }

这时PowerMockito.verifyStatic()第一个参数指定静态方法类的Class,第二个参数接收一个VerificationMode类型的参数,因此传递Mockito中的任何验证方法次数的函数都可以,Mockito中的验证函数会返回的是一个VerificationMode类型。同样在PowerMockito.verifyStatic方法后面要调用一次要验证的静态方法,总感觉这里很奇怪。。。

6. 使用真实返回值

如果在测试的过程中又遇到不需要mock出来的静态方法的模拟返回值,而是需要真实的返回值,怎么办呢,其实跟Mockito一样,PowerMockito同样提供thenCallRealMethod或者doCallRealMethod方法:

 

    @Test
    public void testRealCall() throws Exception {
        PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
        //...
        PowerMockito.when(Utils.getUUId()).thenCallRealMethod();
        //与下面等价
        //PowerMockito.doCallRealMethod().when(Utils.class, "getUUId");
        System.out.println(Utils.getUUId());
    }

或者直接使用spy监控真实对象也可以:

 

    @Test
    public void testRealCall() {
        PowerMockito.spy(Utils.class);
        System.out.println(Utils.getUUId());
    }

7. Mock私有方法

 

public class CommonExample {
    public boolean callPrivateMethod() {
        return isExist();
    }
    private boolean isExist() {
        return false;
    }
 }

 

@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class PrivateUnitTest {
    @Test
    public void testCallPrivateMethod() throws Exception {
        CommonExample commonExample = PowerMockito.mock(CommonExample.class);
        PowerMockito.when(commonExample.callPrivateMethod()).thenCallRealMethod();
        PowerMockito.when(commonExample, "isExist").thenReturn(true);
        Assert.assertTrue(commonExample.callPrivateMethod());
    }
}

在使用上跟纯Mockito的没有太大区别,只不过Mock私有方法是通过下面的api实现的:

PowerMockito.when(Object instance, String methodName, Object... arguments)
在PowerMockito中when函数与Mockito相比,最大的变化就是多了一些传递String类型的methodName的重载方法,这样在使用上几乎无所不能了。

8. Mock普通类的私有变量

 

public class CommonExample {
    private static final int STATE_NOT_READY = 0;
    private static final int STATE_READY = 1;
    private int mState = STATE_NOT_READY;
    
    public boolean doSomethingIfStateReady() {
        if (mState == STATE_READY) {
            // DO some thing
            return true;
        } else {
            return false;
        }
    }
 }

 

    @Test
    public void testDoSomethingIfStateReady() throws Exception {
        CommonExample sample = new CommonExample();
        Whitebox.setInternalState(sample, "mState", 1);
        assertThat(sample.doSomethingIfStateReady(), is(true));
    }

通过Whitebox.setInternalState来改变私有成员变量,这种情况下不需要指定@RunWith和@PrepareForTest。

9. 对静态void方法进行Mock

 

public class CommonExample {
    public static void doSomething(String a) {
        System.out.println("doSomething"+a);
    }
}

 

@RunWith(PowerMockRunner.class)
@PrepareForTest({CommonExample.class})
public class StaticUnitTest {
   @Test
    public void testStaticVoid() throws Exception {
        PowerMockito.mockStatic(CommonExample.class);
        PowerMockito.doNothing().when(CommonExample.class, "doSomething", Mockito.any());
        CommonExample.doSomething("aaa");
    }
}

默认情况下通过PowerMockito.mockStatic的静态类的void的方法是什么也不做的,但是可以显示的执行doNothing, 上面的代码将doNothing那行注释掉也是什么也不做的。那如果想做一些事而不是doNothing呢,跟Mockito一样,采用doAnswer:

 

    @Test
    public void testStaticVoid() throws Exception {
        PowerMockito.mockStatic(CommonExample.class);
        PowerMockito.doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                System.out.println(invocation.getArguments()[0]);
                return null;
            }
        }).when(CommonExample.class, "doSomething", Mockito.any());
        CommonExample.doSomething("aaa");
    }

10. Mock系统的final静态类

 

public class CommonExample {
    public int callSystemStaticMethod(int a, int b) {
        return Math.max(a, a+b);
    }
}

 

@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class StaticUnitTest {
    @Test
    public void callSystemStaticMethod() {
        CommonExample commonExample = new CommonExample();
        PowerMockito.mockStatic(Math.class);
        PowerMockito.when(Math.max(anyInt(), anyInt())).thenReturn(100);
        Assert.assertEquals(100, commonExample.callSystemStaticMethod(10, -5));
    }
}

@PrepareForTest中添加调用系统类所在的类,这里需要注意的是如果你使用PowerMockito来mock系统静态final类,则gradle依赖中不能再添加单纯Mockito的依赖库,否则这里将不能mock成功,会提示Mockito can not mock/spy final class, 因为PowerMockito本身已经有对Mockito的依赖库支持了,所以只依赖PowerMockito就可以了。除了系统静态final类的情况,其他的情况下PowerMockito和Mockito可以同时依赖(我测试是没有问题的)。另外单纯的Mockito新版本中也支持对 final 类 final 方法的 Mock,但是需要添加配置文件并不友好

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值