本指南将帮助您使用JUnit和Mockito为您的Spring Boot项目。我们将使用两种不同的方法来编写单元测试。

您将了解到:

  • 什么是JUnit?
  • 莫基托是什么?
  • 如何创建一个简单的带有单元测试的Spring引导项目。
  • 如何使用@Mock和@InjectMocks使用Mockito编写单元测试,而不启动Spring上下文。
  • 如何使用@MockBean编写带有mocking和启动完整Spring上下文的单元测试。

10步参考课程

项目代码结构

下面的截图显示了我们将要创建的项目的结构。Image

以下是一些细节:

  • 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项目的好工具。

Image

如上图所示,必须执行以下步骤

  • 启动Spring Initializr并选择以下选项
    • 选择com.in28minutes.springboot.tutorial.basics.exampleas组
    • 选择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

后续步骤

完整的代码示例

 /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());
    }
}