美文网首页
单元测试-mockito+powermock

单元测试-mockito+powermock

作者: 仰望forward | 来源:发表于2019-11-11 15:04 被阅读0次

单元测试--Java

使用mockito+powermock进行java单元测试

实例

如下一个正常业务代码,接下来就对HelloController.say方法进行单元测试用例代码开发。

业务代码

public class HelloController{

    @Autowired
    private HelloService helloService;

    @Autowired
    private UserService userService;

    @GetMapping("/say")
    public String say(Integer id){
        HelloUser user=userService.findById(id);
        if(user==null){
            throw new RuntimeException("user not found");
        }
        return helloService.doHello(user);
    }
}

一个正常用例

在HelloController.say()个方法体内,引用了另外两个Bean,userService和helloService。
正常服务启动,我们是必须要有这两个Bean的实例。但是在测试用例代码中其实我们是没必要知道UserService和和HelloService的具体实现的,所以这两个Bean我们可以通过mock的方式虚拟出来。

@RunWith(MockitoJUnitRunner.class)
public class TestHelloController{
    @InjectMock
    private HelloController helloController;
    @Mock
    private HelloService helloService;
    @Mock
    private UserService userService;
    /**
    * 正常用例
    **/
    @Test
    public void testSay(){
        Integer id = 1;
        HelloUser user = new HlloUser();
        //模拟方法调用的传参和返回值
        Mockito.when(userService.findById(id)).thenReturn(user);
        Mockito.when(helloService.doHello(user)).thenReturn("say hi!");
        String resp=helloController.say(id);
        //验证方法调用次数
        Mockito.verfiy(userService,times(1)).findById(id);
        Mockito.verfiy(helloService,times(1)).doHello(user);
        //验证结果
        Assert.assertTrue(resp.equals("say hi!"));
    }
    /**
    * 异常用例
    **/
    @Test(expected=RuntimeException.class)
    public void testSayException(){
        //这里模拟userService调用findById无论传入什么参数,返回都是null
        Mockito.when(userService.findById(Mockito.anyString())).thenReturn(null);
        Integer id = 1;
        helloController.say(id);
    }
}

静态方法模拟

一些方法中有时候需要调用静态方法(如HelloController.create方法),但是静态方法内部,也不是该用例需要关心的,这个使用可以使用powermock对静态方法进行mock。

//业务代码
public class HelloController{
    //...省略部分代码
    @GetMapping("/create")
    public String create(HelloUser user){
        user.setNo(BusinessNoUtils.generateNo());
        userService.save(user);
        return user.getNo();
    }

}
//用例代码--因为这里使用了Powermock,所以这里运行器一定是要使用PowerMockRunner.class
@Runwith(PowerMockRunner.class) 
@PrepareForTest(BusinessNoUtils.class)
public class TestHelloController(){
    //...省略部分代码
    @Test
    public void testCreate(){
        HelloUser user= new HelloUser();
        //需要声明,模拟静态方法
        PowerMockito.mockStatic(BusinessNoUtils.class);
        Mockito.when(BusinessNoUtils.generateNo()).thenReturn("111111");
        Mockito.when(userService.save(Mockito.argThat(e->{
            //简单判断传入参数是否正确
            if(e.getNo().equals("111111")){
                return true;
            }
            return false;
        })));
        String resp=helloController.create(user);
        Assert.assertTrue(resp.equals("111111"));
    }
}

new对象模拟

有一些业务代码中需要创建一个对象,这个对象中有些操作也没必要在业务代码中体现,而且这个对象可能会依赖其他东西。对于这种也可以通过Powermock的方式进行模拟。

// 业务代码
public HelloController{
    //...省略其他代码
   public String copyUser(String id){
        HelloUser helloUser=userService.findById(id);
        HelloUser destUser = new HelloUser();
        String userNo=destUser.copyFrom(helloUser);
        return userNo;
   }
}
//用例代码
@Runwith(PowerMockRunner.class)
//这里的PrepareForTest引用的类是要   new 对象所在的类,这里要注意跟模拟静态方法不一样的地方
@PrepareForTest(HelloController.class)
public TestHelloController{
    //...省略其他代码
    @Test
    public void testCopyUser(){
        HelloUser rawUser = new HelloUser();
        rawUser.setName("123");
        Mockito.when(userService.findById(1)).thenReturn(rawUser);
        HelloUser destUser = Mockito.mock(HelloUser.class);//模拟对象
        //需要声明new对象返回值
        PowerMockito.whenNew(HelloUser.class).withNoArguments().thenReturn(destUser);
        //模拟copyFrom方法
        Mockito.when(destUser.copyFrom(rawUser)).thenReturn("123");
        String taskNo=controller.copyUser(1);
        Mockito.verfiy(taskNo.equals("123"));
    }
}

@Spy的使用

上面的代码是因为要展示用例,所以很多业务逻辑是写在Controller层的。但实际业务开发中,Controller层只是薄薄的一层,没有什么业务逻辑,但Controller层又是整个用例的入口。这个时候,我们不希望UserService在该用例中被模拟,而是希望这个用例下来,整条链路上我们自己写的代码都能覆盖到。这个时候我们就需要@Spy注解了。

//业务代码
public class HelloContrller{
    @Autowired
    UserService userService;

    @GetMapping("/delete/{id}")
    public boolean deleteUser(@PathVariable("id")String id){
        return userService.deleteById(id);
    }
}
public class UserService{
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;
    public boolean deleteById(String id){
        HelloUser userInfo=Optional.ofNullable(userMapper.findById(id)).orElseThrow(()->new RuntionException("not found"));
        //空方法
        userMapper.deleteByNo(userInfo.getNo);
        roleService.deleteByUserId(id);
        return true;
    }

}
public class RoleService{
    @Autowired
    private RoleMapper roleMapper;
    public boolean deleteByUserId(String userId){
        return roleMapper.deleteByUserId(userId);
    }
}

//用例代码
@RunWith(MockitoJUnitRunner.class) 
public class TestHelloController{
    @InjectMocks HelloController helloController;
    @Spy UserService userService ;
    @Spy RoleService roleService ;
    //dao层继续模拟
    @Mock RoleMapper roleMapper;
    @Mock UserMapper userMapper;
    @Before
    public void setUp(){
        //由于没有自动注入,所以手动set进去
        WhiteBox.setInternalState(userService,"userMapper",userMapper);
        WhiteBox.setInternalState(userService,"roleService",roleService);
        WhiteBox.setInternalState(roleService,"roleMapper",roleMapper);
    }

    public void testDelete(){
        String userId = "1";
        UserInfo userInfo = new UserInfo();
        userInfo.setNo("123");
        Mockito.when(userMapper.findById(userId)).thenReturn(userInfo);
        //对于空方法调用处理
        Mockito.doNothing(userMapper).when(deleteByNo("123"));
        Mockito.when(roleMapper.deleteByUserId(uesrId)).thenReturn(true);
        Assert.assertTrue(helloController.deleteUser(userId));
    }

}

注解作用

  • @RunWith(MockitoJUnitRunner.class) :@Runwith是一个运行器,@RunWith(MockitoJUnitRunner.class),表示使用MockitoJUnitRunner来运行。
  • @Mock: 声明的对象,对函数调用均执行mock,不执行真正部分
  • @Spy :声明的对象,对函数调用均执行真正部分
  • @InjectMocks: 自动讲模拟对象或侦查域注入到被测试对象中。
  • @PrepareForTest:表示声明哪些类需要被修改

总结

写好的单元测试用例要比实际写代码的工作量要多,这部分工作量没有太多技术性的东西(从另外的角度来看,可以对你的代码有更深入的理解),但确是比较重要的一部分。
如果项目是一锤子买卖,没有后续迭代,你可以不需要写测试用例,否则都需要写测试用例。
当需要进行代码重构的时候,这些测试用例会给你很大帮助。
当你改了一段祖传代码,如果有测试用例,会给你提供不少思路上的帮助。当然改完代码之后,记得同时修改测试用例。

相关文章

网友评论

      本文标题:单元测试-mockito+powermock

      本文链接:https://www.haomeiwen.com/subject/kpytictx.html