UT单元测试——特殊情况的Mock

本文介绍了在Java单元测试中如何使用Mockito进行异常处理、无返回值方法模拟以及同类私有方法的替换,以确保UT的有效性和代码的隔离性。

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

1)抛出异常的Mock

        使用Assert.assertThrow或在UT方法签名的@Test注解上添加期望抛出的异常类型,当方法抛出该类型的异常时就会被UT捕获,并返回test成功。如若没抛出异常或抛出的异常与指定不符,该UT则无法通过。

// Apple.java
public class Apple {
    private boolean isClean;
}

// AppleMgmtServie.java
public class AppleMgmtService{
    public boolean eat(Apple apple){
        if(apple.getIsClean()){
            return true;
        }else{
            throw new MyException();
        }
    }
}

// AppleMgmtServiceTest.java
@Test(expected = MyException.class)
public class AppleMgmtServiceTest{
    AppleMgmtService appleMgmtService;

    @BeforeEach
    void setUp() {}

    @AfterEach
    void after() {}

    @Test
    public void testEat(){
        Apple appleNotClean = new Apple(false);

        appleMgmtService.eat(appleNotClean); 
        // Assert.assertThrows(MyException, () -> appleMgmtService.eat(appleNotClean));
    }
}

        需要注意的是,这两种方式只能选择其一,因为异常只能被捕捉一次,如果使用了assertThrows对异常捕捉了后,expected注解就会捕捉不到异常而使UT无法通过。另外这种方法更适合校验自定义的异常,因为Java的标准异常可能在代码的任何一个地方出现,如NullPointerException,即使UT测试通过了,我们也无法判断这种异常出现的位置是否与预期相等。

2)无返回值或对入参对象进行修改的Mock

        对于某些对象方法可能没有返回值,但是如果不Mock的话该对象在执行中还是为空,因此UT测试到这个位置仍会抛出NullPointerException异常。对此可以使用doNothing()的方式。其中还有一种特殊情况,虽然调用的方法没有返回值,但是却对入参的对象进行了修改,而我们测试的方法函数使用到了这个被修改的入参,如:

// WashMgmtService.java
public class WashMgmtService{
    public void wash(Apple apple){
        apple.setIsClean(true);
    }
}

// AppleMgmtServie.java
public class AppleMgmtService{
    @AutoWired
    private WashMgmtService washMgmtService;

    public boolean eat(Apple apple){
        washMgmtService.wash(apple);
        if(apple.getIsClean()){
            return true;
        }else{
            return false;
        }
    }
}

         这种情况就比较复杂,我们不仅需要Mock出一个对象,还需要把对象操作入参的过程给Mock出来:

// AppleMgmtServiceTest.java
public class AppleMgmtServiceTest{
    @Mock
    WashMgmtService washMgmtService;

    @InjectMock
    AppleMgmtService appleMgmtService;

    @BeforeEach
    void setUp() {}

    @AfterEach
    void after() {}

    @Test
    public void testEat(){
        Apple appleClean = new Apple(true);
        Apple appleNotClean = new Apple(false);

        // Mock    
        Mockito.doNothing().when(washMgmtService)
               .wash(argThat(new ArgumentMatcher<Apple>(){
                    @Override
                    public boolean matches(Apple apple){
                        apple.setIsClean(true);
                        return false;
                    }
                }));

        // run the test
        boolean resultCLean = appleMgmtService.eat(appleClean);
        boolean resultNotClean = appleMgmtService.eat(appleNotClean);

        // assert    
        Assert.assertTrue(resultClean);    
        Assert.assertEquals(resultNotClean, true);
    }
}

        上述方法中,我们首先用了参数匹配器ArgumentMatcher固定wash方法的传入值是一个Apple类型的对象,随后在matches方法内模拟wash方法对这个入参的修改,在执行测试时就会执行整个模拟过程。所有对入参进行了修改,而功能方法又重新使用到这个入参对象的UT都需要使用这种方式模拟这个过程。

3)同类私有方法的Mock

        对于本类函数中方法的Mock,虽然位于同一个类中的方法都可以被UT执行,但是测试功能方法调用的方法中可能比较复杂,我们不希望执行该方法而是直接用Mock来模拟返回值,如:

// AppleMgmtServie.java
public class AppleMgmtService{
    @AutoWired
    private WashMgmtService washMgmtService;

    public boolean eat(Apple apple){
        Apple appleClean = washNow(apple);
        if(appleClean.getIsClean()){
            return true;
        }else{
            return false;
        }
    }

    private Apple washNow(Apple apple){
        washMgmtService.wash(Apple);
        return apple;
    }
}

// WashMgmtService.java
public class WashMgmtService{
    public void wash(Apple apple){
        apple.setIsClean(true);
    }
}

        虽然测试eat方法时,编译器能顺利找到washNow方法的位置并执行,但washNow方法的执行过程我们并不需要关注,如上面这个例子在washNow中还调用了对象函数,比起模拟这个调用对象函数的过程我们还不如直接模拟washNow方法的结果。这种情况需要用到spy机制。Mock模拟的是功能类中调用的对象类,而spy模拟的即是功能类本身,可对其中的私有方法模拟返回值。

// AppleMgmtServiceTest.java
public class AppleMgmtServiceTest{
    @BeforeEach
    void setUp() {}

    @AfterEach
    void after() {}

    @Test
    public void testEat(){
        Apple appleClean = new Apple(true);
        Apple appleNotClean = new Apple(false);

        // Mock
        Apple appleReturn = new Apple(true);    
        AppleMgmtService spyAppleMgmtService = Mockito.spy(new AppleMgmtService());
        Mockito.doReturn(appleReturn).when(spyAppleMgmtService).washNow(any());

        // run the test
        boolean resultCLean = spyAppleMgmtService.eat(appleClean);
        boolean resultNotClean = spyAppleMgmtService.eat(appleNotClean);

        // assert    
        Assert.assertTrue(resultClean);    
        Assert.assertEquals(resultNotClean, true);
    }
}

        我们之前在测试eat方法是,用的都是普通的AppleMgmtService对象,在spy机制内我们则首先用spy方法构造一个受到监控的AppleMgmtService对象,在这之后的Mock和实际测试都要使用这个对象进行。在受到监控后,这个对象执行的方法也可以被Mock模拟,因此我们就可以构造私有方法的返回值。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值