Mockito单元测试的使用

作为一个java开发,如果不会用mock工具做单元测试是不合格的。mock可以理解为一个模拟对象,即一个替代者,可以替换掉依赖的对象,这样一来我们就可以把注意力集中在业务代码逻辑,验证自我代码的正确性。如下图所示,A类依赖了B类和C类

在这里插入图片描述
假如我们要测试A类,于是我们就需要Mock B和C,也就是用模拟的对象替换B和C
在这里插入图片描述
Mockito是一款优秀的mock工具,也可以叫做mocking框架,它简单易用,可读性强而且验证语法简洁。

一、版本依赖

maven:
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.3.3</version>
    <scope>test</scope>
</dependency>

gradle:
testCompile group: 'org.mockito', name: 'mockito-core', version: '3.3.3'

ant:
<dependency org="org.mockito" name="mockito-core" rev="3.3.3"/>

二、Mockito的初始化

当我们要使用注解(比如@Mock)来mock对象的使用,就要初始化Mockito,这样用@Mock标注的对象才会被实例化,否则直接使用会报Null指针异常。其有两种初始化的方法:

1. 使用MockitoAnnotations.initMocks方法

	 @Mock
    private List mockList;

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test(){
        mockList.add(1);
        verify(mockList).add(1);
    }

2. 类中使用@RunWith(MockitoJUnitRunner.class)

@RunWith(MockitoJUnitRunner.class)
public class MockitoExample {
    @Mock
    private List mockList;
    @Test
    public void test(){
        mockList.add(1);
        verify(mockList).add(1);
    }
}

这里解析下@RunWith的使用,常见用法有以下几种

  1. @RunWith(Suite.class):代表是一个集合测试类,一般是如下用法,也就是其可一次性测试多个用例。
@RunWith(Suite.class)
@Suite.SuiteClasses({ServiceTest.class,A.class})
public class AllTest {
}

public class ServiceTest{
    @Test
    public void test01(){ }
   
    @Test
    public void test02(){}
}
  1. @RunWith(SpringRunner.class)或者@RunWith(SpringJUnit4ClassRunner.class):代表在Spring容器中运行单元测试。如果配合@SpringBootTest就是在SpringBoot容器中运行单元测试,如下所示,classes加载启动类。另外SpringRunner就是SpringJUnit4ClassRunner的别名,他们作用是一样的。
@SpringBootTest(classes = MyApplication.class)
@RunWith(SpringRunner.class)
public class BaseTest {
    public void runUnitTest(){
    }
}
  1. @RunWith(MockitoJUnitRunner.class)
    可以理解为使用Mockito工作运行单元测试,它会初始化Mock和@Spy标注的成员变量。

三、Mock的使用

mock主要功能就是模拟一个对象出来,注意这个是假对象,对其任何方法的操作都不会真正执行的。它有两种使用方法,直接代码mock一个对象,或者是用@Mock造一个对象
第一种方法:

		// 直接代码mock一个List对象
        List list = Mockito.mock(ArrayList.class);
        // 这里的add操作其实没有真实调用
        list.add("22");
        // 打印其大小为0
        System.out.println(list.size());
        // 校验add("22")方法是否执行了,校验通过
        Mockito.verify(list).add("22");

第二种方法:

	@Mock
    private List mockList;

这里需要注意的是Mock出来的对象是假对象,对其方法的操作不会真正执行,可以理解为在真实方法的一个代理,每次对方法的调用其实都是调用了代理方法,这个代理方法是一个空方法,不会做任何事情。方法的返回值都返回默认的:
1. boolean:返回false
2. 基本数值类型:返回0
3. 对象类型:返回null

使用mock的好处是,我们可以对方法的入参和返回值做灵活的设置。比如我们可以在初始化的时候设置某个方法入参和返回值,这样当单元测试执行到这个方法的时候就不用受到方法依赖的约束。假如这样一种情况,我们想要测定时任务发邮件,我其实想测的是这个定时任务逻辑是否正常,对发送邮件是否成功并不关心。由于发送邮件需要依赖其他系统,我们单元测试是发不了邮件的。这样把发送邮件的方法给mock掉就很有必要了。

public class Service01 {
    public boolean sendMail(String sender, String receiver){
        System.out.println(sender + "向" + receiver + "发送了一封邮件");
        return true;
    }
}

public class MockExample {
    @Mock
    private Service01 service01;
    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
        // 如果加了这个返回值的设置,任何入参调用sendMail方法都是返回false
        Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.anyString())).thenReturn(false);
    }

	@Test
    public void testMock(){   
    	// 这里直接返回了false,不会进去sendMail的代码逻辑
		boolean isSendSucc = service01.sendMail("张三","李四");
    }
}

四、Spy的使用

Spy跟Mock不同之处在于,它是会真正执行方法逻辑的。相同之处是它可以指定方法的返回值。
同样的,它也有两种使用方法,直接代码实例化,或者是用@Spy标注
第一种方法:

 @Test
    public void mock2(){
        // 直接代码spy一个List对象
        List list = Mockito.spy(ArrayList.class);
        // 这里的add操作有真实调用
        list.add("22");
        // 打印其大小为1,是真实调用了的
        System.out.println(list.size());
        // 校验add("22")方法是否执行了,校验会通过
        Mockito.verify(list).add("22");
    }

第二种方法:

	 @Spy
     private List spyList;

下面我们来验证Spy真正执行方法逻辑:

@Spy
    private Service01 service01;
    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
        // 如果加了这个返回值的设置,那么方法不会执行,任何入参调用sendMail方法都是返回false
		// Mockito.doReturn(false).when(service01).sendMail(Mockito.anyString(),Mockito.anyString());
    }

    @Test
    public void testMock(){
        // 这里会执行sendMail方法
        boolean isSendSucc = service01.sendMail("张三","李四");
        System.out.println("邮件发送是否成功:" + isSendSucc);
    }

上面单元测试执行结果
张三向李四发送了一封邮件
邮件发送是否成功:true

注意:Spy不能标注接口,可以是实现类和抽象类

五、InjectMocks的使用

@InjectMocks跟Mock和Spy的逻辑有点不一样,他用来给标注的成员变量填充带有@Mock和@Spy标签的bean,可以理解为它会吸取所有@Mock和@Spy标注的bean为自己所用。先看下面的例子:
首先定义一个book的dao

@Mapper
public interface BookDao {
    String getBookById(String id);
}

接着定义book的服务类

public interface BookService {
    String getBookById(String id);
}

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    @Override
    public String getBookById(String id) {
        return bookDao.getBookById(id);
    }
}

测试服务类,这里看到bookService用了@IninjectMocks,那么bookService里面成员变量bookDao就会使用@Mock标注的bookDao。这样我们就解决了bean的依赖问题了,bookServic里面的bookDao的任何操作完全可以在单元测试类里指定返回值。
在这里插入图片描述
如果是嵌套的bean可以用ReflectionTestUtils.setFileld()绑定成员变量。上面例子中,要测试service上层的Controller,希望mock bookDao,而不是service,那么可以这样用:

@RestController
public class BookController {
    @Autowired
    private BookService bookService;
    
    @GetMapping("/getBook")
    public String getBookById(@RequestParam String id){
        return bookService.getBookById(id);
    }
}

@RunWith(MockitoJUnitRunner.class)
public class BookControllerTest {
    @Spy
    private BookServiceImpl bookService;
    @InjectMocks
    private BookController bookController;
    @Mock
    private BookDao bookDao;

    @Before
    public void init(){
        Mockito.when(bookDao.getBookById("123")).thenReturn("《java语言》");
        // 这里指定bookService的成员变量bookDao
        ReflectionTestUtils.setField(bookService,"bookDao",bookDao);
    }

    @Test
    public void testGetBook(){
        System.out.println(bookController.getBookById("123"));
    }
}

六、@MockBean的使用

@MockBean是SpringBoot中增加的,用来支持容器中的mock测试。它跟mock的使用逻辑是一样,只是它修饰的对象是容器中的对象,也就是bean对象。

// 注意这个类是容器中的组件
@Component
public class Service01 {
    public boolean sendMail(String sender, String receiver){
        System.out.println(sender + "向" + receiver + "发送了一封邮件");
        return true;
    }
}

// 需要加载SpringBoot的上下文
@RunWith(SpringRunner.class)
@SpringBootTest
public class MockBeanExample {
    // 使用容器中对象,用MockBean
    @MockBean
    private Service01 service01;
    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
        // 如果加了这个返回值的设置,任何入参调用sendMail方法都是返回false
        // Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.anyString())).thenReturn(false);
    }

	@Test
    public void testMock(){   
    	// 这里直接返回了true,会执行sendMail的代码
		boolean isSendSucc = service01.sendMail("张三","李四");
    }
}

七、@SpyBean的使用

@SpyBean也是SpringBoot增加的一个注解,用来支持Spring容器的单元测试,它与Spy的逻辑基本一致,不同之处就在于它标注的对象是容器对象。具体使用可以参考上面@MockBean的使用方法。

八、返回值设置

我们在上面的例子中也看到了,可以指定mock方法的返回值,常用大概有以下几种:

1. 调用完方法后指定返回值

格式:Mockito.when(调用的类.方法).thenReturn(指定的返回值);
例如上面的例子:Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.anyString())).thenReturn(false);

如果是@Mock标注的对象方法,这样设置后不会进去方法执行,直接返回指定值。
如果是@Spy标注的对象方法,这样设置后会进去执行方法,但是返回指定的返回值。

2. 直接返回指定值

格式:Mockito.doReturn(方法返回值).when(spy标注的对象).调用的方法;
例如上面的例子:Mockito.doReturn(false).when(service01).sendMail(Mockito.anyString(),Mockito.anyString());

这样设置后,不管是@Mock还是@Spy标注的对象方法都不会进去执行,会直接返回指定值。

另外需要注意的是指定方法时,入参的设置也是有讲究的。假如有多个入参的方法,一个入参使用了matcher做匹配,那么其他入参也要用matcher匹配,例如下面用了any方法,那么第二入参就不能写死了,可以用eq方法来做匹配
错误的写法:Mockito.when(service01.sendMail(Mockito.anyString(),“王五”)).thenReturn(true);
正确的写法:Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.eq(“王五”))).thenReturn(true);

3. 设置抛出异常

格式:Mockito.when(调用方法).thenThrow(抛出的异常类);
比如:
Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.anyString())).thenThrow(RuntimeException.class);

九、方法的校验和断言

通常我们写单元测试就是要断言方法的执行是否符合我们的预期,那用什么方法来做结果的断言呢?除了junit提供的Assert类中的方法外,Mockito也给我们提供了几种校验方法。

1. 断言方法是否被调用过

格式: Mockito.verify(对象).对象的方法;
比如:Mockito.verify(list).add(“22”),校验list对象是否调用了add(“22”)方法。

2. 断言异常

	@Before
    public void init(){
        MockitoAnnotations.initMocks(this);
		// 让方法抛出异常
        Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.anyString())).thenThrow(RuntimeException.class);
    }

	// 必须抛出指定的异常才会通过测试
    @Test(expected=RuntimeException.class)
    public void testThrowException(){
        service01.sendMail("张三","李四");
    }

3. Assert类中的断言方法

这个就自己进去源码看了,有多个方法可以用。

十、局限性

Mockito也有它的局限性,主要是两个:

  1. 不能mock静态方法。
  2. 不能mock私有方法。

这两种方法可能只能是通过其他工具,或者通过上层方法调用来做测试了。

十一、测试Controller

这里总结了2种测试方法,都要在SpringBoot的框架中测试。一般在集成测试中使用,需要启动Spring容器。

  1. 使用TestRestTemplate模板
@RestController
public class BookController2 {
    @GetMapping("/getBookInfo")
    public String getBookById(@RequestParam String id){
        System.out.println("查询书籍信息,bookId=" + id);
        return "《java语言》";
    }
}

@RunWith(SpringRunner.class)
//指定web环境,随机端口
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BookControllerTest {
    //这个对象是运行在web环境的时候加载到spring容器中
    @Autowired
    private TestRestTemplate testRestTemplate;

    @Test
    public void testGetBookInfo(){
        String result = testRestTemplate.getForObject("/getBookInfo?id=123456", String.class);
        System.out.println(result);
    }
}
  1. 使用AutoConfigureMockMvc
    AutoConfigureMockMvc会自动注入MockMvc,可以方便的指定入参或者是header,建议使用这种方法测试Controller
@RestController
public class BookController2 {
    @PostMapping("/getBookInfo2")
    public String getBookById(@RequestParam String id, @RequestHeader String user){
        System.out.println(user + "查询书籍信息,bookId=" + id);
        return "《java语言》";
    }
}

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class BookControllerTest2 {
    @Autowired
    public MockMvc mockMvc;

    @Test
    public void testGetBookInfo() throws Exception {
        MvcResult result = mockMvc.perform(
                MockMvcRequestBuilders.post("/getBookInfo2").param("id","123").header("user","xiaoming"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andReturn();
        System.out.println(result.getResponse().getContentAsString());
    }
}

参考:

  1. Mockito教程
  2. springboot test
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mockito 是一个基于 Java 的开源单元测试框架。使用 Mockito 来进行单元测试可以让您更轻松地编写和运行测试,而无需实际依赖于所有可能的外部依赖项。 以下是一个简单的使用 Mockito 进行单元测试的步骤: 1.添加 Mockito 依赖项到您的 pom.xml 文件中。这可以通过下面的代码完成: <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.25.1</version> <scope>test</scope> </dependency> 2.创建一个包含一些需要测试的方法的 Java 类。 3.在测试类中创建一个 Mock 对象,并使用 Mockito.when() 方法模拟对象的行为。 4.使用 assertEquals() 断言来比较模拟的对象的预期结果与实际结果是否一致。 这里是一个使用 Mockito 进行单元测试的示例: public class CalculatorTest { @Mock private CalculatorService calculatorService; private Calculator calculator; @Before public void setUp(){ MockitoAnnotations.initMocks(this); calculator = new Calculator(calculatorService); } @Test public void testAdd(){ when(calculatorService.add(2,3)).thenReturn(5); assertEquals(10, calculator.perform(2,3)); } } 在这个示例中,我们模拟了一个 CalculatorService 对象,并在测试方法中调用了 calculator.perform(2,3) 方法。当我们调用 add() 方法时,它会返回 5,我们使用 assertEquals() 来确保实际结果等于预期结果。 注意:在上面的示例中,我们在 setUp() 方法中初始化了 Mockito,以确保 Mock 对象已正确注入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值