Junit+Mockito 快速测试你的程序

什么是mockito

是什么

  • 是一个java语言下的一单元测试框架

使用场景

  1. 在资源环境不完善的情况下也可进行单元测试,确保开发进度与程序的正确性
  • 在整个项目中通常依赖多个部分组成如:数据库、缓存数据库、第三方系统等等。。
  • 在团队并发开发过程中,总不可能每个每个依赖资源都是准备好的,所以有没有办法缺点这些组件的情况下也可以进行部分代码的单元测试呢?这里使用mockit+junit test 可以解决

概念

mock对象

  • 通过反射技术创建出来的一个代理对象;
  • 这个对象可模拟真实场景去执行预期的程序与结果

打桩

  • 可以简单的理解为为其设置期待值如:
when(mockLinkedList.get(0)).thenReturn("first")
// 理解:将每一个元素设置为"first" 在需要的使用每0个元素时就返回值为“first”

验证

  • 验证是否有执行过、或者执行了几次;mock 对象创建后就会监控对mock对象执行的每一个操作
verify(mockLinkedList).get(0)
// 理解:验证get(0)被调用的次数

常用注解

  • @Mock: 自动注入一个代理对象
  • @Spy:自动注入一个真实对象
  • @InjectMocks:声明为一个需要依赖其他对象,并自动将带有@Mock 的对象注入到应该对象中

创建mock对象的三种方式

版本方式一方式二方式三
Junit4@RunWith(MockitoJunitRunner.class)Mockito.mock(xx.class)/Mockito.spy(xx.class)MockitoAnnotations.openMocks(class)+@Mock注解
Junit5@ExtendWith(MockitoExtension.class)Mockito.mock(xx.class)/Mockito.spy(xx.class)MockitoAnnotations.openMocks(class)+@Mock注解

插桩的三种方式:

doReturn(返回值).when(类句柄).doXX();
使用场景:
  • 对于spy 调用时并不会调用一次目标方法,而是直接返回指定值。
  • 真实调用方法时,有其他依赖方法调用了数据库、缓存、消息等,如果你想跳过这些调用则可以使用此方法来跳过这些调用;
// Setup
final List<DriverBo> expectedResult =
Arrays.asList(
DriverBo.builder()
.id(0L)
.contactInformations(Arrays.asList(DriverContactInformationBo.builder().build()))
.build());

// 调用queryList方法时,直接返回指定的数据
Mockito.doReturn(Arrays.asList(Convert.toList(expectedResult)))
.when(driverServiceImplUnderTest)
.queryList(Arrays.asList(0L));

// Run the test
final List<DriverBo> result = driverServiceImplUnderTest.queryList(Arrays.asList(0L));

// Verify the results
assertThat(result.size()).isEqualTo(expectedResult.size());
when(类句柄).thenReturn(返回值);
使用场景:
  • 对于spy 调用时会先调用一次目标方法。
doNothing().when(类句柄).doXX();
使用场景:
  • 处理返回值为null 的方法。

插入桩抛出异常的三种方式:

  • doThrow(RuntimeException.class.when(list.get(4)); 这种方式适用于没有返回值和spy 对象;
  • when(list.get(4)).thenThrow(RuntimeException.class); 这种方式适用于有返回值和mock对象;

调用真实方法

// 使用mockito对象时,真实调用目标方法
Mockito.when(xxService.method(arg))
        .thenCallRealMethod();

指定插桩逻辑

// 自定义插桩逻辑,invocation可以拿到对应的调用方法,mock对象,入参等信息
Mockito.doAnswer(
  new Answer() {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
      Object argument = invocation.getArgument(0);
      return 1;
    }
  })
  .when(driverServiceImplUnderTest)
  .queryById(0L);

简单入门

示例

  • 示例一:
@Test
public void list() {
  // mock creation 创建mock对象
  List mockedList = mock(List.class);

  // using mock object 使用mock对象
  mockedList.add("one");
  mockedList.clear();

  // verification 验证
  verify(mockedList).add("one");
  verify(mockedList).clear();
}

@Test
public void linkedList() {
  LinkedList mockLinkedList = mock(LinkedList.class);
  RuntimeException runtimeException = new RuntimeException();
  // 打桩测试
  when(mockLinkedList.get(0)).thenReturn("first");
  when(mockLinkedList.get(1)).thenReturn(runtimeException);

  // 验证结果
  Assertions.assertEquals("first", mockLinkedList.get(0));
  Assertions.assertEquals(runtimeException, mockLinkedList.get(1));

  // 验证
  verify(mockLinkedList, times(1)).get(1);
}

实战案例

案例一:CRUD操作

UserDao
public class UserDao {
  public User queryByName(String name) {
    System.out.println(name);
    return User.builder().id(5).name(name).build();
  }

  public Integer save(User user) {
    return 1;
  }

  public void update(User user) {}
}

User
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
  private Integer id;
  private String name;
}

UserService
public class UserService {

  private UserDao userDao = new UserDao();

  /**
   * 新增一个用户
   *
   * @param name
   * @return
   */
  public Integer save(String name) throws Exception {
    if (StrUtil.isBlank(name)) {
      throw new ValidationException("名称不能为空");
    }
    User user = userDao.queryByName(name);
    if (user != null) {
      throw new Exception("用户已存在,请不要重复添加");
    }
    Integer id = userDao.save(user);
    return id;
  }
}

UserServiceTest


class UserServiceTest {

  @Mock private UserDao userDao;

  @InjectMocks @Spy private UserService userService;

  @BeforeEach
  void before() {
    MockitoAnnotations.openMocks(this);
  }

  @Test
  void save() throws Exception {
    String name = null;

    // 名字为空
    try {
      userService.save(name);
      Assertions.fail("这里会挂");
    } catch (Exception e) {
      Assertions.assertTrue(e instanceof ValidationException);
    }

    // 设置值走正常流程
    name = "gzwen";

    // 正常流程
    Mockito.doReturn(null).when(userDao).queryByName(name);
    Mockito.when(userService.save(name)).thenReturn(1);
    Integer newUserId = userService.save(name);
    Assertions.assertEquals(1, newUserId);

    //  用户已存在,不允许重新插入,抛出异常
    Mockito.doReturn(new User()).when(userDao).queryByName(name);
    try {
      userService.save(name);
      Assertions.fail("这里会挂");
    } catch (Exception e) {
      Assertions.assertTrue(e instanceof Exception);
    }

    //  用户已存在,不允许重新插入,抛出异常;输入任意参数都返回一个空user 对象
    Mockito.doReturn(new User()).when(userDao).queryByName(Mockito.any());
    try {
      userService.save(name);
      Assertions.fail("这里会挂");
    } catch (Exception e) {
      Assertions.assertTrue(e instanceof Exception);
    }
  }
}

案例二:mock 静态方法

添加信赖
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
          	<version>4.3.1</version>
            <scope>test</scope>
        </dependency>
静态方法:StaticUtils
public class StaticUtils {
  public StaticUtils() {}

  public static List<Integer> range(int tart, int end) {
    return IntStream.range(tart, end).boxed().collect(Collectors.toList());
  }

  public static String name() {
    return "Echo";
  }
}

单元测试实例:StaticUtilsTest

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

  @Mock private UserDao userDao;

  @InjectMocks @Spy private UserService userService;

  @Test
  void save() throws Exception {
    String name = null;

    // 名字为空
    try {
      userService.save(name);
      Assertions.fail("这里会挂");
    } catch (Exception e) {
      Assertions.assertTrue(e instanceof ValidationException);
    }

    // 设置值走正常流程
    name = "gzwen";

    // 正常流程
    Mockito.doReturn(null).when(userDao).queryByName(name);
    Mockito.when(userService.save(name)).thenReturn(1);
    Integer newUserId = userService.save(name);
    Assertions.assertEquals(1, newUserId);

    //  用户已存在,不允许重新插入,抛出异常
    Mockito.doReturn(new User()).when(userDao).queryByName(name);
    try {
      userService.save(name);
      Assertions.fail("这里会挂");
    } catch (Exception e) {
      Assertions.assertTrue(e instanceof Exception);
    }

    //  用户已存在,不允许重新插入,抛出异常;输入任意参数都返回一个空user 对象
    Mockito.doReturn(new User()).when(userDao).queryByName(Mockito.any());
    try {
      userService.save(name);
      Assertions.fail("这里会挂");
    } catch (Exception e) {
      Assertions.assertTrue(e instanceof Exception);
    }
  }
}

其他:

如果你访问github网站比较慢,或者一些资源无法访问你可以看一下这里:网络加速器

关于我

  • 关注不迷路,点赞走一波~ 转载请标注~
  • 公众号
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值