JUNIT5+Mockito单元测试

1、前言

之前写过一篇使用testMe自动生成单元测试用例,使用的是junit4来编写的单元测试用例,目前很多新项目都已经使用JDK11+以及SpringBoot3+。本次基于junit5+Mockito来编写单元测试。

2、Maven依赖

2.1 JDK21+SpringBoot版本基于3.1.0

SpringBoot依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.objenesis</groupId>
                    <artifactId>objenesis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

mockito依赖

<!--junit5单元测试-->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>5.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>5.3.1</version>
        </dependency>

lombok依赖

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>edge-SNAPSHOT</version>
        </dependency>

2.2 JDK17+SpringBoot版本基于2.2.5.RELEASE

SpringBoot依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
    </parent>

Junit依赖

<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
        </dependency>

mockito依赖

<dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>5.2.0</version>
            <exclusions>
                <exclusion>
                    <groupId>net.bytebuddy</groupId>
                    <artifactId>byte-buddy</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>net.bytebuddy</groupId>
                    <artifactId>byte-buddy-agent</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.14.1</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.14.1</version>
        </dependency>

lombok依赖

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>

2.3 JDK8+Junit5+mockito4.11.0

JDK8最大支持mockito版本为4.11.0,5以上的版本最低需要JDK11,所以这里如果是JDK8,则mockito版本则使用4.11.0。

Junit依赖

<!--Junit5-->
<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.2</version>
        </dependency>

mockito依赖

        <!-- 5.0后无需引入mockito-inline -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>4.11.0</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>4.11.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mockito</groupId>
                    <artifactId>mockito-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>4.11.0</version>
        </dependency>

lombok依赖

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>

3、业务代码

UserService代码

@Slf4j
@Service
public class UserServiceImpl implements UserService {

	@Value("${custom.config.value:}")
	private String configValue;

    @Resource
    private UserManager userManager;

    @Override
    public Long createUser(UserDto userDto) {
        log.info("创建用户入参:{}", JSON.toJSONString(userDto));
        String name = userDto.getUsername();
        if (StringUtils.isBlank(name)) {
            log.error("用户名称不能为空");
            throw new BizException("用户名称不能为空");
        }

        Long id = userManager.createUser(userDto);
        log.info("创建用户出参:{}", id);
        return id;
    }

    @Override
    public Boolean updateUser(UserDto userDto) {
        log.info("更新用户入参:{}", JSON.toJSONString(userDto));
        Long id = userDto.getId();
        String name = userDto.getUsername();
        if (Objects.isNull(id)) {
            log.error("用户主键不能为空");
            throw new BizException("用户主键不能为空");
        }
        if (StringUtils.isBlank(name)) {
            log.error("用户名称不能为空");
            throw new BizException("用户名称不能为空");
        }

        UserDto user = userManager.getUser(userDto);
        if (Objects.isNull(user)) {
            log.error("用户不存在");
            throw new BizException("用户不存在");
        }
        Boolean result = userManager.updateUser(userDto);
        log.info("更新用户出参:{}", result);
        return result;
    }

    @Override
    public UserDto getUser(UserDto userDto) {
        log.info("获取用户入参:{}", JSON.toJSONString(userDto));
        Long id = userDto.getId();
        if (Objects.isNull(id)) {
            log.error("用户主键不能为空");
            throw new BizException("用户主键不能为空");
        }
        UserDto user = userManager.getUser(userDto);
        log.info("获取用户出参:{}", user);
        return user;
    }

    @Override
    public Boolean batchCreateUser(List<UserDto> list) {
        log.info("批量创建用户入参:{}", JSON.toJSONString(list));
        if (CollectionUtils.isEmpty(list)) {
            log.error("入参集合不能为空");
            throw new BizException("入参集合不能为空");
        }

        int size = 10;
        long keepAliveTime = 60;
        long start = System.currentTimeMillis();
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10000);
        ThreadFactory threadFactory = new DefaultThreadFactory("executor");
        ExecutorService executorService
                = new ThreadPoolExecutor(size, size, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);

        List<CompletableFuture<Boolean>> futureList = new ArrayList<>();
        for (UserDto userDto : list) {
            CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
                log.info("当前线程名称:{}", Thread.currentThread());
                try {
                    Long id = userManager.createUser(userDto);
                    TimeUnit.SECONDS.sleep(3L);
                    log.info("线程:{} id={} done", Thread.currentThread(), id);
                    return Boolean.TRUE;
                } catch (InterruptedException e) {
                    log.error("创建用户异常:{}", e.getMessage(), e);
                    return Boolean.FALSE;
                }
            }, executorService);
            futureList.add(future);
        }

        Boolean result = Boolean.TRUE;
        try {
            CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS);
            for (CompletableFuture<Boolean> future : futureList) {
                Boolean back = future.get();
                if (Boolean.FALSE.equals(back)) {
                    result = Boolean.FALSE;
                }
            }
        } catch (Exception e) {
            log.error("创建用户异常:{}", e.getMessage(), e);
            result = Boolean.FALSE;
        }

        long end = System.currentTimeMillis();
        log.info("批量创建用户耗时:{}", (end - start));

        log.info("批量创建用户出参:{}", result);
        return result;
    }

	@Override
    public void deleteUser(UserDto userDto) {
        log.info("删除用户:{}", JSON.toJSONString(userDto));
        userManager.deleteUser(userDto);
        log.info("删除用户完成");
    }

}

UserManager 代码

@Slf4j
@Component
public class UserManagerImpl implements UserManager {

    @Resource
    private UserRepository userRepository;

    @Override
    public Long createUser(UserDto userDto) {
        Long result = userRepository.createUser(userDto);
        log.info("UserManagerImpl.createUser方法执行结果:{}", result);
        return result;
    }

    @Override
    public Boolean updateUser(UserDto userDto) {
        return userRepository.updateUser(userDto);
    }

    @Override
    public UserDto getUser(UserDto userDto) {
        return userRepository.getUser(userDto);
    }

	@Override
    public void deleteUser(UserDto userDto) {
        userRepository.deleteUser(userDto);
    }

}

UserRepository代码

@Slf4j
@Component
public class UserRepositoryImpl implements UserRepository {

    @Override
    public Long createUser(UserDto userDto) {
        log.info("com.summer.toolkit.mock.UserRepositoryImpl.createUser方法执行");
        return 1L;
    }

    @Override
    public Boolean updateUser(UserDto userDto) {
        log.info("com.summer.toolkit.mock.UserRepositoryImpl.updateUser方法执行");
        return Boolean.TRUE;
    }

    @Override
    public UserDto getUser(UserDto userDto) {
        log.info("com.summer.toolkit.mock.UserRepositoryImpl.getUser方法执行");
        return userDto;
    }

	@Override
	public void deleteUser(UserDto userDto) {
		log.info("com.summer.toolkit.mock.UserRepositoryImpl.deleteUser方法执行删除用户");
	}
}

4、单元测试

package com.summer.toolkit.service;

import com.alibaba.fastjson.JSON;
import com.summer.toolkit.dto.UserDto;
import com.summer.toolkit.exception.BizException;
import com.summer.toolkit.mock.UserManagerImpl;
import com.summer.toolkit.mock.UserRepositoryImpl;
import com.summer.toolkit.mock.UserServiceImpl;
import com.summer.toolkit.util.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import org.springframework.test.util.ReflectionTestUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;


@Slf4j
@ExtendWith(MockitoExtension.class)
@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")
public class UserServiceTest {

    @Mock
    private UserRepositoryImpl userRepository;

    @Spy
    private UserManagerImpl userManager;

    @InjectMocks
    private UserServiceImpl userService;

    @Captor
    private ArgumentCaptor<UserDto> userDtoArgumentCaptor;

    @BeforeEach
    public void before() {
        // 通过ReflectionTestUtils注入需要的非public字段数据
        ReflectionTestUtils.setField(userManager, "userRepository", userRepository);
        // 通过反射给@Value从配置文件注入的属性赋值
        ReflectionTestUtils.setField(userService, "configValue", "definedValue");
    }

    @Test
    public void testCreateUser() {
        // 模拟依赖方法
        //Mockito.when(userManager.createUser(any())).thenReturn(Long.valueOf(1));
        Mockito.when(userRepository.createUser(any())).thenReturn(Long.valueOf(2));

        // 调用被测方法
        UserDto userDto = this.buildUserDto();
        Long result = userService.createUser(userDto);

        // 验证方法结果
        Long expect = 2L;
        Assertions.assertEquals(expect, result);
        // 验证方法是否被调用,并带有超时时间
        Mockito.verify(userManager, Mockito.timeout(100).times(1)).createUser(userDto);
        // 自定义异常描述
        Mockito.verify(userManager, Mockito.times(1).description("someMethod should be called twice")).createUser(any());
    }

	@Test
	public void testCreateUserException() {
		// 模拟依赖方法
		Mockito.when(userRepository.createUser(any())).thenThrow(new BizException("defined message"));
	
		// 验证方法是否抛出指定异常
		BizException exception = Assertions.assertThrows(BizException.class, () -> {
			// 调用被测方法
			UserDto userDto = this.buildUserDto();
			userService.createUser(userDto);
		});
		Assertions.assertEquals("defined message", exception.getMessage());
	}

    @Test
    public void testCreateUserContinuity() {
        // 模拟依赖方法多次调用返回值
        Mockito.when(userRepository.createUser(any())).thenReturn(1L, 2L, 3L);

        // 多次调用被测方法并验证每次返回值是否符合预期
        UserDto userDto = this.buildUserDto();

        Long result = userService.createUser(userDto);
        Assertions.assertEquals(1L, result);

        result = userService.createUser(userDto);
        Assertions.assertEquals(2L, result);

        result = userService.createUser(userDto);
        Assertions.assertEquals(3L, result);

        // 验证方法是否被调用
        Mockito.verify(userManager, Mockito.timeout(100).times(3)).createUser(userDto);
        // 自定义异常描述
        Mockito.verify(userManager, Mockito.times(3).description("someMethod should be called twice")).createUser(any());
    }

    @Test
    public void testUpdateUser() {
        // 模拟依赖方法
        Mockito.when(userManager.updateUser(any())).thenReturn(Boolean.TRUE);
        Mockito.when(userManager.getUser(any())).thenReturn(new UserDto());

        // 调用被测方法
        UserDto userDto = this.buildUserDto();
        userDto.setId(1L);
        Boolean result = userService.updateUser(userDto);

        // 验证方法结果
        Assertions.assertEquals(Boolean.TRUE, result);
        // 验证方法是否被调用
        Mockito.verify(userManager).getUser(any());
        Mockito.verify(userManager).updateUser(any());
    }

    @Test
    public void testGetUser() {
        // 模拟依赖方法
        Mockito.when(userManager.getUser(any())).thenReturn(new UserDto());

        // 调用被测方法
        UserDto userDto = this.buildUserDto();
        userDto.setId(1L);
        UserDto result = userService.getUser(userDto);

        // 验证方法结果
        Assertions.assertNotNull(result);
        // 验证方法是否被调用
        Mockito.verify(userManager).getUser(userDto);
    }

    @Test
    public void testBatchCreateUserException() {
        // 模拟依赖方法,指定单个异常类型
        Mockito.when(userManager.createUser(any())).thenThrow(BizException.class);

        // 调用被测方法
        List<UserDto> param = new ArrayList<>();
        UserDto userDto = this.buildUserDto();
        param.add(userDto);
        Boolean result = userService.batchCreateUser(param);

        // 验证方法结果
        Assertions.assertEquals(Boolean.FALSE, result);
        // 验证方法是否被调用,默认一次
        Mockito.verify(userManager).createUser(userDto);
        // 验证方法是否被调用了1次
        Mockito.verify(userManager, Mockito.times(1)).createUser(any());
    }

    @Test
    public void testBatchCreateUserTimes() {
        // 模拟依赖方法
        Mockito.when(userManager.createUser(any())).thenReturn(1L);

        // 调用被测方法
        List<UserDto> param = new ArrayList<>();
        UserDto userDto = this.buildUserDto();
        param.add(userDto);
        param.add(userDto);
        param.add(userDto);
        Boolean result = userService.batchCreateUser(param);

        // 验证方法结果
        Assertions.assertEquals(Boolean.TRUE, result);
        // 验证方法是否被调用了3次
        Mockito.verify(userManager, Mockito.times(3)).createUser(any());
        Mockito.verify(userManager, Mockito.atLeast(3)).createUser(any());
        Mockito.verify(userManager, Mockito.atMost(3)).createUser(any());
    }

    @Test
    public void testBatchCreateUserCallBack() {
        // 模拟依赖方法,指定回调函数,回调函数返回值会覆盖原有逻辑的真实返回值
        Mockito.when(userManager.createUser(any())).thenAnswer(new Answer<Long>() {
            @Override
            public Long answer(InvocationOnMock invocationOnMock) throws Throwable {
                log.info("执行回调方法");
                Object[] arguments = invocationOnMock.getArguments();
                log.info("回调参数为:{}", JSON.toJSONString(arguments));
                Method method = invocationOnMock.getMethod();
                log.info("方法名称为:{}", method.getName());
                return 2L;
            }
        });

        // 调用被测方法
        UserDto userDto = this.buildUserDto();
        Long result = userService.createUser(userDto);

        // 验证方法结果,此结果为回调函数的返回值
        Long expect = 2L;
        Assertions.assertEquals(expect, result);
    }

    @Test
    public void testCaptor() {
        // 模拟依赖方法
        Mockito.when(userManager.createUser(any())).thenReturn(1L);

        // 调用被测方法
        UserDto userDto = this.buildUserDto();
        Long result = userService.createUser(userDto);

        // 验证方法结果
        Assertions.assertEquals(1L, result);

        // 捕获调用方法参数,验证保存用户时数据主键是否为空
        Mockito.verify(userManager).createUser(userDtoArgumentCaptor.capture());
        Assertions.assertNull(userDtoArgumentCaptor.getValue().getId());
    }

    @Test
    @DisplayName("静态方法测试")
    public void testFileUtils() {
        // 构建对象
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");

        // 模拟对应的静态类
        // JDK11及以上版本中,try块中的变量可以在外部声明
        MockedStatic<FileUtils> mocked = Mockito.mockStatic(FileUtils.class);
        try (mocked) {
            // 模拟依赖静态方法
            mocked.when(() -> FileUtils.readFileAllLines(anyString())).thenReturn(list);

            // 调用被测方法
            List<String> lines = FileUtils.readFileAllLines(anyString());

            // 验证方法结果
            Assertions.assertEquals(list.size(), lines.size());
        } catch (Exception e) {
            log.error("模拟静态方法异常:{}", e.getMessage(), e);
        }
    }

	@Test
    public void testDeleteUser() {
        // 模拟依赖的无返回值方法
        Mockito.doNothing().when(userRepository).deleteUser(any());

        // 调用被测方法
        userService.deleteUser(new UserDto());
        log.info("mock void method");

        // 验证方法结果
        Mockito.verify(userRepository, Mockito.atMost(1)).deleteUser(any());
    }

    /**
     * 构建用户数据传输对象
     *
     * @return UserDto 返回构建好的用户数据传输对象
     */
    private UserDto buildUserDto() {
        UserDto userDto = new UserDto();
        userDto.setUsername("小明");
        userDto.setBirthday(new Date());
        userDto.setAddress("北京市大兴区亦庄经济开发区");
        userDto.setComment("加麻加辣");
        userDto.setGender(1);
        return userDto;
    }

}

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android Studio提供了强大的单元测试功能,可以帮助开发人员验证代码的正确性和稳定性。以下是关于Android Studio单元测试的一些常见问题和答案: 1. 如何创建一个单元测试类? 在Android Studio中,可以在测试文件夹中创建一个Java类,并添加@Test注解来标记测试方法。例如: ```java import org.junit.Test; public class MyUnitTest { @Test public void addition_isCorrect() { assertEquals(4, 2 + 2); } } ``` 运行该测试类时,可以通过右键点击类名或方法名并选择"Run 'MyUnitTest'"来执行测试。 2. 如何运行单元测试? 可以通过多种方式运行单元测试: - 在测试类或测试方法上右键点击,并选择"Run 'ClassName'"或"Run 'MethodName'"。 - 在项目视图中,找到测试类或测试方法,并点击绿色的三角形图标来运行。 - 使用快捷键Shift + F10(Windows)或Control + R(Mac)来运行最后一次测试。 3. 如何检查单元测试的结果? 在运行单元测试后,可以在"Run"窗口中查看测试结果。绿色表示通过的测试,红色表示失败的测试。如果有失败的测试,可以点击失败的测试方法来查看详细的错误信息。 4. 如何使用断言来验证结果? 在单元测试中,可以使用断言来验证代码的预期行为。Android Studio使用JUnit框架来提供断言方法。常用的断言方法包括: - assertEquals(expected, actual):验证两个值是否相等。 - assertTrue(condition):验证条件是否为真。 - assertFalse(condition):验证条件是否为假。 - assertNull(object):验证对象是否为null。 - assertNotNull(object):验证对象是否不为null。 等等。 5. 如何模拟依赖进行单元测试? 在某些情况下,我们可能需要模拟依赖对象来进行单元测试。Android Studio可以使用Mockito等框
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值