Testen im Spring Boot

1. Übersicht

In diesem Tutorial sehen wir uns das Schreiben von Tests mit der Framework-Unterstützung in Spring Boot an. Wir werden Unit-Tests behandeln, die isoliert ausgeführt werden können, sowie Integrationstests, die den Spring-Kontext booten, bevor Tests ausgeführt werden.

Wenn Sie Spring Boot noch nicht kennen, lesen Sie unser Intro zu Spring Boot.

2. Projekteinrichtung

Die Anwendung, die wir in diesem Artikel verwenden werden, ist eine API, die einige grundlegende Vorgänge für eine Mitarbeiterressource bereitstellt . Dies ist eine typische abgestufte Architektur. Der API-Aufruf wird vom Controller zum Service an die Persistenzschicht verarbeitet .

3. Maven-Abhängigkeiten

Fügen wir zunächst unsere Testabhängigkeiten hinzu:

 org.springframework.boot spring-boot-starter-test test 2.2.6.RELEASE   com.h2database h2 test 

Der Spring-Boot-Starter-Test ist die primäre Abhängigkeit, die die meisten für unsere Tests erforderlichen Elemente enthält.

Die H2 DB ist unsere In-Memory-Datenbank. Sie müssen keine tatsächliche Datenbank zu Testzwecken konfigurieren und starten.

4. Integrationstests mit @DataJpaTest

Wir werden mit einer Entität namens Employee arbeiten, deren Eigenschaften eine ID und einen Namen haben :

@Entity @Table(name = "person") public class Employee { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Size(min = 3, max = 20) private String name; // standard getters and setters, constructors }

Und hier ist unser Repository mit Spring Data JPA:

@Repository public interface EmployeeRepository extends JpaRepository { public Employee findByName(String name); }

Das war's für den Persistenzschichtcode. Lassen Sie uns nun unsere Testklasse schreiben.

Lassen Sie uns zunächst das Skelett unserer Testklasse erstellen:

@RunWith(SpringRunner.class) @DataJpaTest public class EmployeeRepositoryIntegrationTest { @Autowired private TestEntityManager entityManager; @Autowired private EmployeeRepository employeeRepository; // write test cases here }

@RunWith (SpringRunner.class) bietet eine Brücke zwischen den Spring Boot-Testfunktionen und JUnit. Wenn wir in unseren JUnit-Tests Spring Boot-Testfunktionen verwenden, ist diese Anmerkung erforderlich.

@DataJpaTest bietet einige Standardeinstellungen, die zum Testen der Persistenzschicht erforderlich sind:

  • Konfigurieren von H2, einer In-Memory-Datenbank
  • Festlegen des Ruhezustands, von Spring Data und der DataSource
  • Durchführen eines @EntityScan
  • Aktivieren der SQL-Protokollierung

Für die Ausführung von DB-Vorgängen benötigen wir einige Datensätze, die sich bereits in unserer Datenbank befinden. Um diese Daten einzurichten , können wir TestEntityManager verwenden.

Der Spring Boot TestEntityManager ist eine Alternative zum Standard-JPA- EntityManager , der Methoden bereitstellt, die häufig beim Schreiben von Tests verwendet werden.

EmployeeRepository ist die Komponente, die wir testen werden.

Schreiben wir nun unseren ersten Testfall:

@Test public void whenFindByName_thenReturnEmployee() { // given Employee alex = new Employee("alex"); entityManager.persist(alex); entityManager.flush(); // when Employee found = employeeRepository.findByName(alex.getName()); // then assertThat(found.getName()) .isEqualTo(alex.getName()); }

Im obigen Test verwenden wir den TestEntityManager , um einen Mitarbeiter in die Datenbank einzufügen und ihn über die API zum Suchen nach Namen zu lesen.

Der Teil assertThat (…) stammt aus der Assertj-Bibliothek, die im Lieferumfang von Spring Boot enthalten ist.

5. Verspotten mit @MockBean

Unser Service- Layer-Code hängt von unserem Repository ab .

Um die Service- Schicht zu testen , müssen wir jedoch nicht wissen oder uns darum kümmern, wie die Persistenzschicht implementiert ist:

@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Override public Employee getEmployeeByName(String name) { return employeeRepository.findByName(name); } }

Im Idealfall sollten wir in der Lage sein, unseren Service- Layer-Code zu schreiben und zu testen , ohne unsere vollständige Persistenz-Schicht zu verkabeln.

Um dies zu erreichen, können wir die Verspottungsunterstützung von Spring Boot Test verwenden.

Schauen wir uns zuerst das Skelett der Testklasse an:

@RunWith(SpringRunner.class) public class EmployeeServiceImplIntegrationTest { @TestConfiguration static class EmployeeServiceImplTestContextConfiguration { @Bean public EmployeeService employeeService() { return new EmployeeServiceImpl(); } } @Autowired private EmployeeService employeeService; @MockBean private EmployeeRepository employeeRepository; // write test cases here }

Um die Service- Klasse zu überprüfen , muss eine Instanz der Service- Klasse erstellt und als @Bean verfügbar sein, damit wir sie in unserer Testklasse @Autowire verwenden können. Wir können diese Konfiguration mithilfe der Annotation @TestConfiguration erreichen .

Beim Scannen von Komponenten stellen wir möglicherweise fest, dass Komponenten oder Konfigurationen, die nur für bestimmte Tests erstellt wurden, versehentlich überall erfasst werden. Um dies zu verhindern, bietet Spring Boot die Annotation @TestConfiguration , die wir Klassen in src / test / java hinzufügen können, um anzuzeigen, dass sie nicht durch Scannen erfasst werden sollten.

Eine weitere interessante Sache ist die Verwendung von @MockBean . Es wird ein Mock für das EmployeeRepository erstellt , mit dem der Aufruf des tatsächlichen EmployeeRepository umgangen werden kann :

@Before public void setUp() { Employee alex = new Employee("alex"); Mockito.when(employeeRepository.findByName(alex.getName())) .thenReturn(alex); }

Da die Einrichtung abgeschlossen ist, ist der Testfall einfacher:

@Test public void whenValidName_thenEmployeeShouldBeFound() { String name = "alex"; Employee found = employeeService.getEmployeeByName(name); assertThat(found.getName()) .isEqualTo(name); }

6. Unit-Test mit @WebMvcTest

Unser Controller hängt von der Serviceschicht ab. Lassen Sie uns der Einfachheit halber nur eine einzige Methode einschließen:

@RestController @RequestMapping("/api") public class EmployeeRestController { @Autowired private EmployeeService employeeService; @GetMapping("/employees") public List getAllEmployees() { return employeeService.getAllEmployees(); } }

Da wir uns nur auf den Controller- Code konzentrieren, ist es selbstverständlich, den Service- Layer-Code für unsere Unit-Tests zu verspotten :

@RunWith(SpringRunner.class) @WebMvcTest(EmployeeRestController.class) public class EmployeeRestControllerIntegrationTest { @Autowired private MockMvc mvc; @MockBean private EmployeeService service; // write test cases here }

Zum Testen der Controller können wir @WebMvcTest verwenden . Die Spring MVC-Infrastruktur wird automatisch für unsere Komponententests konfiguriert.

In den meisten Fällen ist @ WebMvcTest darauf beschränkt, einen einzelnen Controller zu booten . Wir können es auch zusammen mit @MockBean verwenden , um Scheinimplementierungen für alle erforderlichen Abhängigkeiten bereitzustellen.

@WebMvcTest konfiguriert auch MockMvc automatisch , was eine leistungsstarke Möglichkeit zum einfachen Testen von MVC-Controllern bietet, ohne einen vollständigen HTTP-Server zu starten.

Nachdem dies gesagt ist, schreiben wir unseren Testfall:

@Test public void givenEmployees_whenGetEmployees_thenReturnJsonArray() throws Exception { Employee alex = new Employee("alex"); List allEmployees = Arrays.asList(alex); given(service.getAllEmployees()).willReturn(allEmployees); mvc.perform(get("/api/employees") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(1))) .andExpect(jsonPath("$[0].name", is(alex.getName()))); }

Der Methodenaufruf get (…) kann durch andere Methoden ersetzt werden, die HTTP-Verben wie put () , post () usw. entsprechen. Bitte beachten Sie, dass wir auch den Inhaltstyp in der Anforderung festlegen.

MockMvc ist flexibel und wir können damit jede Anfrage erstellen.

7. Integrationstests mit @SpringBootTest

Wie der Name schon sagt, konzentrieren sich Integrationstests auf die Integration verschiedener Ebenen der Anwendung. Das bedeutet auch, dass keine Verspottung involviert ist.

Idealerweise sollten wir die Integrationstests von den Komponententests getrennt halten und nicht zusammen mit den Komponententests ausgeführt werden. Wir können dies tun, indem wir ein anderes Profil verwenden, um nur die Integrationstests auszuführen. Einige Gründe hierfür könnten sein, dass die Integrationstests zeitaufwändig sind und möglicherweise eine tatsächliche Datenbank zur Ausführung benötigen.

In diesem Artikel werden wir uns jedoch nicht darauf konzentrieren und stattdessen den speicherinternen H2-Persistenzspeicher verwenden.

Die Integrationstests müssen einen Container starten, um die Testfälle auszuführen. Daher ist hierfür ein zusätzliches Setup erforderlich - all dies ist in Spring Boot ganz einfach:

@RunWith(SpringRunner.class) @SpringBootTest( SpringBootTest.WebEnvironment.MOCK, classes = Application.class) @AutoConfigureMockMvc @TestPropertySource( locations = "classpath:application-integrationtest.properties") public class EmployeeRestControllerIntegrationTest { @Autowired private MockMvc mvc; @Autowired private EmployeeRepository repository; // write test cases here }

Die Annotation @SpringBootTest ist nützlich, wenn der gesamte Container gebootet werden muss. Die Annotation erstellt den ApplicationContext , der in unseren Tests verwendet wird.

Wir können das webEnvironment- Attribut von @SpringBootTest verwenden , um unsere Laufzeitumgebung zu konfigurieren. Wir verwenden hier WebEnvironment.MOCK , damit der Container in einer Schein-Servlet-Umgebung ausgeführt wird.

Als Nächstes hilft die Annotation @TestPropertySource beim Konfigurieren der Speicherorte von Eigenschaftendateien, die für unsere Tests spezifisch sind. Beachten Sie, dass die mit @TestPropertySource geladene Eigenschaftendatei die vorhandene Datei application.properties überschreibt .

The application-integrationtest.properties contains the details to configure the persistence storage:

spring.datasource.url = jdbc:h2:mem:test spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

If we want to run our integration tests against MySQL, we can change the above values in the properties file.

The test cases for the integration tests might look similar to the Controller layer unit tests:

@Test public void givenEmployees_whenGetEmployees_thenStatus200() throws Exception { createTestEmployee("bob"); mvc.perform(get("/api/employees") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content() .contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$[0].name", is("bob"))); }

The difference from the Controller layer unit tests is that here nothing is mocked and end-to-end scenarios will be executed.

8. Auto-Configured Tests

One of the amazing features of Spring Boot's auto-configured annotations is that it helps to load parts of the complete application and test-specific layers of the codebase.

In addition to the above-mentioned annotations, here's a list of a few widely used annotations:

  • @WebFluxTest: We can use the @WebFluxTest annotation to test Spring WebFlux controllers. It's often used along with @MockBean to provide mock implementations for required dependencies.
  • @JdbcTest: We can use the @JdbcTest annotation to test JPA applications, but it's for tests that only require a DataSource. The annotation configures an in-memory embedded database and a JdbcTemplate.
  • @JooqTest: To test jOOQ-related tests, we can use @JooqTest annotation, which configures a DSLContext.
  • @DataMongoTest: To test MongoDB applications, @DataMongoTest is a useful annotation. By default, it configures an in-memory embedded MongoDB if the driver is available through dependencies, configures a MongoTemplate, scans for @Document classes, and configures Spring Data MongoDB repositories.
  • @DataRedisTestmakes it easier to test Redis applications. It scans for @RedisHash classes and configures Spring Data Redis repositories by default.
  • @DataLdapTest configures an in-memory embedded LDAP (if available), configures a LdapTemplate, scans for @Entry classes, and configures Spring Data LDAP repositories by default.
  • @RestClientTest: We generally use the @RestClientTest annotation to test REST clients. It auto-configures different dependencies such as Jackson, GSON, and Jsonb support; configures a RestTemplateBuilder; and adds support for MockRestServiceServer by default.

9. Conclusion

In this article, we took a deep dive into the testing support in Spring Boot and showed how to write unit tests efficiently.

Den vollständigen Quellcode dieses Artikels finden Sie auf GitHub. Der Quellcode enthält viele weitere Beispiele und verschiedene Testfälle.

Wenn Sie mehr über das Testen erfahren möchten, finden Sie in JUnit 5 separate Artikel zu Integrationstests und Komponententests.