本指南将帮助您使用JUnit和Mockito为您的Spring Boot项目。我们将使用两种不同的方法来编写单元测试。
您将了解到:
- 什么是JUnit?
- 莫基托是什么?
- 如何创建一个简单的带有单元测试的Spring引导项目。
- 如何使用@Mock和@InjectMocks使用Mockito编写单元测试,而不启动Spring上下文。
- 如何使用@MockBean编写带有mocking和启动完整Spring上下文的单元测试。
10步参考课程
- Spring Framework for Beginners in 10 Steps
- Spring Boot for Beginners in 10 Steps
- Spring MVC in 10 Steps
- JPA and Hibernate in 10 Steps
- Eclipse Tutorial for Beginners in 5 Steps
- Maven Tutorial for Beginners in 5 Steps
- JUnit Tutorial for Beginners in 5 Steps
- Mockito Tutorial for Beginners in 5 Steps
- Complete in28Minutes Course Guide
项目代码结构
下面的截图显示了我们将要创建的项目的结构。
以下是一些细节:
BusinessService.java
-我们要测试的业务服务。DataService.java
-DataService是BusinessService的依赖项。我们希望在单元测试中模拟DataService。BusinessServiceMockTest.java
-使用@mock和@injectmocks的基本模拟进行单元测试。此单元测试不启动Spring上下文。BusinessServiceMockSpringContextTest.java
-单元测试启动完整的spring上下文来测试BusinessService。-
SpringBootTutorialBasicsApplication.java
-主要的Spring引导应用程序类,用于启动应用程序。 pom.xml
-包含生成此项目所需的所有依赖项。我们将使用Spring Boot Starter Web和开发人员工具以外的测试。- Maven3.0+是您的构建工具。
- 你最喜欢的IDE。我们使用Eclipse。
- JDK 1.8+
JUnit简介
JUnit是最流行的Java单元测试框架。
我们通常在大型项目中工作--其中一些项目有超过2,000个源文件,有时可能有10,000个文件和一百万行代码。
在单元测试之前,我们依赖于部署整个应用程序并检查屏幕是否好看。但这并不是很有效,而且是手动的。单元测试关注于为单个类和方法编写自动化测试。JUnit是一个框架,它将帮助您调用一个方法并检查(或断言)输出是否如预期的那样。自动化测试的重要之处在于,这些测试可以在持续集成的情况下运行--一旦某些代码发生变化就立即运行。
要测试的示例源代码:
package com.in28minutes.junit;
public class MyMath {
int sum(int[] numbers) {
int sum = 0;
for (int i: numbers) {
sum += i;
}
return sum;
}
}
求和方法的单元测试:
package com.in28minutes.junit;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class MyMathTest {
MyMath myMath = new MyMath();
// MyMath.sum
// 1,2,3 => 6
@Test
public void sum_with3numbers() {
System.out.println("Test1");
assertEquals(6, myMath.sum(new int[] {
1,
2,
3
}));
}
@Test
public void sum_with1number() {
System.out.println("Test2");
assertEquals(3, myMath.sum(new int[] {
3
}));
}
}
其他重要的JUnit注释
- @注释之前@注释之后
- 在类中的每个测试方法之前和之后运行
- @BeForeClass@类后批注
- 在测试类之前和之后执行一次的静态方法
package com.in28minutes.junit;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class MyMathTest {
MyMath myMath = new MyMath();
@Before
public void before() {
System.out.println("Before");
}
@After
public void after() {
System.out.println("After");
}
@BeforeClass
public static void beforeClass() {
System.out.println("Before Class");
}
@AfterClass
public static void afterClass() {
System.out.println("After Class");
}
// MyMath.sum
// 1,2,3 => 6
@Test
public void sum_with3numbers() {
System.out.println("Test1");
assertEquals(6, myMath.sum(new int[] {
1,
2,
3
}));
}
@Test
public void sum_with1number() {
System.out.println("Test2");
assertEquals(3, myMath.sum(new int[] {
3
}));
}
}
Mockito简介
Mockito是Java中最流行的模仿框架。
在下面的示例中,SomeBusinessImpl依赖于DataService。当我们为某个BusinessImpl编写单元测试时,我们将希望使用一个模拟数据服务--它不连接到数据库。
package com.in28minutes.mockito.mockitodemo;
public class SomeBusinessImpl {
private DataService dataService;
public SomeBusinessImpl(DataService dataService) {
super();
this.dataService = dataService;
}
用Mockito编写测试
package com.in28minutes.mockito.mockitodemo;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
public class SomeBusinessMockTest {
@Test
public void testFindTheGreatestFromAllData() {
DataService dataServiceMock = mock(DataService.class);
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
24,
15,
3
});
SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceMock);
int result = businessImpl.findTheGreatestFromAllData();
assertEquals(24, result);
}
@Test
public void testFindTheGreatestFromAllData_ForOneValue() {
DataService dataServiceMock = mock(DataService.class);
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
15
});
SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceMock);
int result = businessImpl.findTheGreatestFromAllData();
assertEquals(15, result);
}
}
备注
DataService dataServiceMock = mock(DataService.class)
-我们正在使用mock方法创建一个mock。when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 24, 15, 3 })
-删除模拟以返回特定数据
使用Mockito注释-@mock,@injectmocks,@runwith(MockitoJUnitRunner.class)
package com.in28minutes.mockito.mockitodemo;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class SomeBusinessMockAnnotationsTest {
@Mock
DataService dataServiceMock;
@InjectMocks
SomeBusinessImpl businessImpl;
@Test
public void testFindTheGreatestFromAllData() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
24,
15,
3
});
assertEquals(24, businessImpl.findTheGreatestFromAllData());
}
@Test
public void testFindTheGreatestFromAllData_ForOneValue() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
15
});
assertEquals(15, businessImpl.findTheGreatestFromAllData());
}
@Test
public void testFindTheGreatestFromAllData_NoValues() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {});
assertEquals(Integer.MIN_VALUE, businessImpl.findTheGreatestFromAllData());
}
}
备注
@Mock DataService dataServiceMock;
-为DataService创建一个模拟。@InjectMocks SomeBusinessImpl businessImpl;
-将模拟作为依赖项注入到BusinessImpl中。@RunWith(MockitoJUnitRunner.class)
-JUnit运行器,它会在测试运行之前使用@Mock和@InjectMocks实现所有初始化魔术。
用Spring Initializr创建项目
用Spring Initializr创建一个REST服务是一件易如反掌的事。
Spring Initializr是引导您的Spring Boot项目的好工具。
如上图所示,必须执行以下步骤
- 启动Spring Initializr并选择以下选项
- 选择
com.in28minutes.springboot.tutorial.basics.example
as组 - 选择
spring-boot-tutorial-basics
作为人工制品 - 选择以下依赖项
- Web
- DevTools
- 选择
- 单击Generate Project。
- 将项目导入Eclipse。文件->导入->现有Maven项目。
pom.xml中的启动项目
下面是pom.xml中的几个初学者项目。
< 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> <
/dependency>
单元测试的重要依赖关系是spring-boot-starter-test
SpringBootTestStarter是使用包括JUnit,Hamcrest和Mockito在内的库测试SpringBoot应用程序的入门程序。让我们看看spring-boot-starter-test中的重要依赖关系。
< dependency >
<
groupId > junit < /groupId> <
artifactId > junit < /artifactId> <
version > 4.12 < /version> <
scope > compile < /scope> <
/dependency> <
dependency >
<
groupId > org.assertj < /groupId> <
artifactId > assertj - core < /artifactId> <
version > 3.8 .0 < /version> <
scope > compile < /scope> <
/dependency> <
dependency >
<
groupId > org.mockito < /groupId> <
artifactId > mockito - core < /artifactId> <
version > 2.11 .0 < /version> <
scope > compile < /scope> <
/dependency> <
dependency >
<
groupId > org.hamcrest < /groupId> <
artifactId > hamcrest - core < /artifactId> <
version > 1.3 < /version> <
scope > compile < /scope> <
/dependency> <
dependency >
<
groupId > org.hamcrest < /groupId> <
artifactId > hamcrest - library < /artifactId> <
version > 1.3 < /version> <
scope > compile < /scope> <
/dependency> <
dependency >
<
groupId > org.skyscreamer < /groupId> <
artifactId > jsonassert < /artifactId> <
version > 1.5 .0 < /version> <
scope > compile < /scope> <
/dependency> <
dependency >
<
groupId > org.springframework < /groupId> <
artifactId > spring - test < /artifactId> <
version > 5.0 .1.RELEASE < /version> <
scope > compile < /scope> <
/dependency>
SpringBootTestStarter为单元测试引入了广泛的依赖项。
- 基本测试框架-JUnit
- 嘲弄-嘲弄
- 断言-AssertJ,Hamcrest
- 弹簧单元试验框架。弹簧试验
添加正在测试的代码
让我们创建一个简单的数据服务。实际上,这应该与某个数据库进行对话以获取所有数据,但现在让我们保持简单并返回硬代码值。这将是BusinessService的依赖项。
/src/main/java/com/in28minutes/springboot/tutorial/basics/example/unittesting/DataService.java
@Repository
public class DataService {
public int[] retrieveAllData() {
// Some dummy data
// Actually this should talk to some database to get all the data
return new int[] {
1,
2,
3,
4,
5
};
}
}
让我们使用DataService作为依赖项创建BusinessService。
/src/main/java/com/in28minutes/springboot/tutorial/basics/example/unittesting/BusinessService.java
@Service
public class BusinessService {
private DataService dataService;
public BusinessService(DataService dataService) {
super();
this.dataService = dataService;
}
public int findTheGreatestFromAllData() {
int[] data = dataService.retrieveAllData();
int greatest = Integer.MIN_VALUE;
for (int value: data) {
if (value > greatest) {
greatest = value;
}
}
return greatest;
}
}
需要注意的重要事项:
public BusinessService(DataService dataService) {
-我们提供了一个用于注入数据服务的构造函数。public int findTheGreatestFromAllData()
-这就是我们想要为其编写单元测试的方法。我们希望用多种组合进行测试
使用MockitorRunner对Mockito进行单元测试
下面的代码显示了使用MockitoJUnitRunner对Mockito进行的单元测试。
/src/test/java/com/in28minutes/springboot/tutorial/basics/example/unittest/
BusinessServicesMockTest.java
备注
@RunWith(MockitoJUnitRunner.class) public class BusinessServicesMockTest
-JUnit运行器,它会在测试运行之前使用@Mock和@InjectMocks实现所有初始化魔术。@Mock DataService dataServiceMock
-为DataService创建一个模拟。@InjectMocks BusinessService businessImpl
-将模拟作为依赖项注入到BusinessService中。- 有三种测试方法测试三种不同的场景:多个值,一个值和没有传入值。
使用@MockBean启动完整Spring上下文的单元测试
下面的示例代码显示了我们如何编写相同的单元测试,启动完整的Spring上下文。
/src/test/java/com/in28minutes/springboot/tutorial/basics/example/unittest/
BusinessServicesMockSpringContextTest.java
备注
@RunWith(SpringRunner.class)
-Spring Runner用于在单元测试中启动Spring上下文。@SpringBootTest
-此批注指示被测试的上下文是@SpringBootApplication
在单元测试期间启动完整的SpringBootTutorialBasicsApplication。@MockBean DataService dataServiceMock
-@MockBean注释为DataService创建一个模拟。这个mock是在Spring上下文中使用的,而不是在真正的DataService中使用的。@Autowired BusinessService businessImpl
-从Spring上下文中选择业务服务并自动将其导入。
在这两种方法之间作出选择
启动整个spring上下文会使单元测试变得更慢。如果上下文中的其他bean出现错误,单元测试也将开始失败。因此,MockitoJUnitRunner方法是首选的。
恭喜!您正在阅读的是一系列关于Spring Boot和微服务的50多篇文章中的一篇文章。我们的Github存储库上也有20多个项目。有关50多篇文章和代码示例的完整系列,click here。
后续步骤
- 学习Spring Boot的基本知识-Spring Boot vs Spring vs Spring MVC,Auto Configuration,Spring Boot Starter Projects,Spring Boot Starter Parent,Spring Boot Initializr
- Learn RESTful and SOAP Web Services with Spring Boot
- Learn Microservices with Spring Boot and Spring Cloud
- Watch Spring Framework Interview Guide - 200+ Questions & Answers
完整的代码示例
/pom.xml
<< ? xml version = "1.0"
encoding = "UTF-8" ? >
<
project xmlns = "http://maven.apache.org/POM/4.0.0"
xmlns: xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<
modelVersion > 4.0 .0 < /modelVersion>
<
groupId > com.in28minutes.springboot.tutorial.basics.example < /groupId> <
artifactId > spring - boot - tutorial - basics < /artifactId> <
version > 0.0 .1 - SNAPSHOT < /version> <
packaging > jar < /packaging>
<
name > spring - boot - tutorial - basics < /name> <
description > Spring Boot Tutorial - Basic Concept Examples < /description>
<
parent >
<
groupId > org.springframework.boot < /groupId> <
artifactId > spring - boot - starter - parent < /artifactId> <
version > 2.0 .0.M6 < /version> <
relativePath / > <!-- lookup parent from repository -->
<
/parent>
<
properties >
<
project.build.sourceEncoding > UTF - 8 < /project.build.sourceEncoding> <
project.reporting.outputEncoding > UTF - 8 < /project.reporting.outputEncoding> <
java.version > 1.8 < /java.version> <
/properties>
<
dependencies >
<
dependency >
<
groupId > org.springframework.boot < /groupId> <
artifactId > spring - boot - starter - web < /artifactId> <
/dependency>
<
dependency >
<
groupId > org.springframework.boot < /groupId> <
artifactId > spring - boot - devtools < /artifactId> <
scope > runtime < /scope> <
/dependency> <
dependency >
<
groupId > org.springframework.boot < /groupId> <
artifactId > spring - boot - starter - test < /artifactId> <
scope > test < /scope> <
/dependency> <
/dependencies>
<
build >
<
plugins >
<
plugin >
<
groupId > org.springframework.boot < /groupId> <
artifactId > spring - boot - maven - plugin < /artifactId> <
/plugin> <
/plugins> <
/build>
<
repositories >
<
repository >
<
id > spring - snapshots < /id> <
name > Spring Snapshots < /name> <
url > https: //repo.spring.io/snapshot</url>
<
snapshots >
<
enabled > true < /enabled> <
/snapshots> <
/repository> <
repository >
<
id > spring - milestones < /id> <
name > Spring Milestones < /name> <
url > https: //repo.spring.io/milestone</url>
<
snapshots >
<
enabled > false < /enabled> <
/snapshots> <
/repository> <
/repositories>
<
pluginRepositories >
<
pluginRepository >
<
id > spring - snapshots < /id> <
name > Spring Snapshots < /name> <
url > https: //repo.spring.io/snapshot</url>
<
snapshots >
<
enabled > true < /enabled> <
/snapshots> <
/pluginRepository> <
pluginRepository >
<
id > spring - milestones < /id> <
name > Spring Milestones < /name> <
url > https: //repo.spring.io/milestone</url>
<
snapshots >
<
enabled > false < /enabled> <
/snapshots> <
/pluginRepository> <
/pluginRepositories>
<
/project>
/src/main/java/com/in28minutes/springboot/tutorial/basics/example/
SpringBootTutorialBasicsApplication.java
package com.in28minutes.springboot.tutorial.basics.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringBootTutorialBasicsApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringBootTutorialBasicsApplication.class, args);
for (String name: applicationContext.getBeanDefinitionNames()) {
System.out.println(name);
}
}
}
/src/main/java/com/in28minutes/springboot/tutorial/basics/example/unittesting/
BusinessService.java
package com.in28minutes.springboot.tutorial.basics.example.unittesting;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
private DataService dataService;
public BusinessService(DataService dataService) {
super();
this.dataService = dataService;
}
public int findTheGreatestFromAllData() {
int[] data = dataService.retrieveAllData();
int greatest = Integer.MIN_VALUE;
for (int value: data) {
if (value > greatest) {
greatest = value;
}
}
return greatest;
}
}
/src/main/java/com/in28minutes/springboot/tutorial/basics/example/unittesting/
DataService.java
package com.in28minutes.springboot.tutorial.basics.example.unittesting;
import org.springframework.stereotype.Repository;
@Repository
public class DataService {
public int[] retrieveAllData() {
// Some dummy data
// Actually this should talk to some database to get all the data
return new int[] {
1,
2,
3,
4,
5
};
}
}
/src/test/java/com/in28minutes/springboot/tutorial/basics/example/
SpringBootTutorialBasicsApplicationTests.java
package com.in28minutes.springboot.tutorial.basics.example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootTutorialBasicsApplicationTests {
@Test
public void contextLoads() {}
}
/src/test/java/com/in28minutes/springboot/tutorial/basics/example/unittest/
BusinessServicesMockSpringContextTest.java
package com.in28minutes.springboot.tutorial.basics.example.unittest;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import com.in28minutes.springboot.tutorial.basics.example.unittesting.BusinessService;
import com.in28minutes.springboot.tutorial.basics.example.unittesting.DataService;
@RunWith(SpringRunner.class)
@SpringBootTest
public class BusinessServicesMockSpringContextTest {
@MockBean
DataService dataServiceMock;
@Autowired
BusinessService businessImpl;
@Test
public void testFindTheGreatestFromAllData() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
24,
15,
3
});
assertEquals(24, businessImpl.findTheGreatestFromAllData());
}
@Test
public void testFindTheGreatestFromAllData_ForOneValue() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
15
});
assertEquals(15, businessImpl.findTheGreatestFromAllData());
}
@Test
public void testFindTheGreatestFromAllData_NoValues() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {});
assertEquals(Integer.MIN_VALUE, businessImpl.findTheGreatestFromAllData());
}
}
/src/test/java/com/in28minutes/springboot/tutorial/basics/example/unittest/
BusinessServicesMockTest.java
package com.in28minutes.springboot.tutorial.basics.example.unittest;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import com.in28minutes.springboot.tutorial.basics.example.unittesting.BusinessService;
import com.in28minutes.springboot.tutorial.basics.example.unittesting.DataService;
@RunWith(MockitoJUnitRunner.class)
public class BusinessServicesMockTest {
@Mock
DataService dataServiceMock;
@InjectMocks
BusinessService businessImpl;
@Test
public void testFindTheGreatestFromAllData() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
24,
15,
3
});
assertEquals(24, businessImpl.findTheGreatestFromAllData());
}
@Test
public void testFindTheGreatestFromAllData_ForOneValue() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {
15
});
assertEquals(15, businessImpl.findTheGreatestFromAllData());
}
@Test
public void testFindTheGreatestFromAllData_NoValues() {
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {});
assertEquals(Integer.MIN_VALUE, businessImpl.findTheGreatestFromAllData());
}
}