Test in Spring Boot

1. Panoramica

In questo tutorial, daremo uno sguardo alla scrittura di test utilizzando il supporto del framework in Spring Boot. Tratteremo gli unit test che possono essere eseguiti in isolamento, nonché i test di integrazione che eseguiranno il bootstrap del contesto Spring prima di eseguire i test.

Se sei nuovo in Spring Boot, dai un'occhiata alla nostra introduzione a Spring Boot.

2. Configurazione del progetto

L'applicazione che utilizzeremo in questo articolo è un'API che fornisce alcune operazioni di base su una risorsa dipendente . Questa è una tipica architettura a livelli: la chiamata API viene elaborata dal controller al servizio al livello di persistenza .

3. Dipendenze di Maven

Aggiungiamo prima le nostre dipendenze di test:

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

Lo spring-boot-starter-test è la dipendenza primaria che contiene la maggior parte degli elementi richiesti per i nostri test.

H2 DB è il nostro database in memoria. Elimina la necessità di configurare e avviare un database effettivo a scopo di test.

4. Test di integrazione con @DataJpaTest

Lavoreremo con un'entità denominata Employee, che ha un ID e un nome come proprietà:

@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 }

Ed ecco il nostro repository che utilizza Spring Data JPA:

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

Questo è tutto per il codice del livello di persistenza. Ora andiamo a scrivere la nostra lezione di prova.

Per prima cosa, creiamo lo scheletro della nostra classe di test:

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

@RunWith (SpringRunner.class) fornisce un ponte tra le funzionalità di test di Spring Boot e JUnit. Ogni volta che utilizziamo qualsiasi funzionalità di test Spring Boot nei nostri test JUnit, sarà richiesta questa annotazione.

@DataJpaTest fornisce alcune impostazioni standard necessarie per testare il livello di persistenza:

  • configurazione di H2, un database in memoria
  • l'impostazione di Hibernate, Spring Data e DataSource
  • eseguendo un @EntityScan
  • attivazione della registrazione SQL

Per eseguire operazioni DB, abbiamo bisogno di alcuni record già presenti nel nostro database. Per impostare questi dati, possiamo usare TestEntityManager.

Spring Boot TestEntityManager è un'alternativa allo standard JPA EntityManager che fornisce metodi comunemente usati durante la scrittura dei test.

EmployeeRepository è il componente che testeremo.

Ora scriviamo il nostro primo caso di test:

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

Nel test precedente, stiamo usando TestEntityManager per inserire un dipendente nel DB e leggerlo tramite l'API di ricerca per nome.

La parte assertThat (...) proviene dalla libreria Assertj, fornita in bundle con Spring Boot.

5. Schernire con @MockBean

Il nostro codice del livello di servizio dipende dal nostro repository .

Tuttavia, per testare il livello di servizio , non abbiamo bisogno di sapere o preoccuparci di come viene implementato il livello di persistenza:

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

Idealmente, dovremmo essere in grado di scrivere e testare il nostro codice del livello di servizio senza cablaggio nel nostro livello di persistenza completo.

Per ottenere ciò, possiamo utilizzare il supporto per il mocking fornito da Spring Boot Test.

Diamo prima un'occhiata allo scheletro della classe di test:

@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 }

Per controllare la classe Service , dobbiamo avere un'istanza della classe Service creata e disponibile come @Bean in modo da poterla @Autowire nella nostra classe di test. Possiamo ottenere questa configurazione utilizzando l' annotazione @TestConfiguration .

Durante la scansione dei componenti, potremmo scoprire che i componenti o le configurazioni create solo per test specifici vengono accidentalmente rilevati ovunque. Per evitare ciò, Spring Boot fornisce l' annotazione @TestConfiguration che possiamo aggiungere alle classi in src / test / java per indicare che non devono essere rilevate dalla scansione.

Un'altra cosa interessante qui è l'uso di @MockBean . Crea un Mock per EmployeeRepository , che può essere utilizzato per bypassare la chiamata al EmployeeRepository effettivo :

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

Poiché la configurazione è completata, il test case sarà più semplice:

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

6. Unit test con @WebMvcTest

Il nostro controller dipende dal livello di servizio ; includiamo solo un unico metodo per semplicità:

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

Poiché ci concentriamo solo sul codice del controller , è naturale deridere il codice del livello di servizio per i nostri unit test:

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

Per testare i controller , possiamo usare @WebMvcTest . Configurerà automaticamente l'infrastruttura Spring MVC per i nostri unit test.

Nella maggior parte dei casi, @ WebMvcTest sarà limitato al bootstrap di un singolo controller. Possiamo anche usarlo insieme a @MockBean per fornire implementazioni fittizie per qualsiasi dipendenza richiesta.

@WebMvcTest configura anche automaticamente MockMvc , che offre un modo potente per testare facilmente i controller MVC senza avviare un server HTTP completo.

Detto questo, scriviamo il nostro test case:

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

La chiamata al metodo get (…) può essere sostituita da altri metodi corrispondenti ai verbi HTTP come put () , post () , ecc. Si noti che stiamo anche impostando il tipo di contenuto nella richiesta.

MockMvc è flessibile e possiamo creare qualsiasi richiesta utilizzandolo.

7. Test di integrazione con @SpringBootTest

Come suggerisce il nome, i test di integrazione si concentrano sull'integrazione di diversi livelli dell'applicazione. Ciò significa anche che non è coinvolto alcun scherzo.

Idealmente, dovremmo mantenere i test di integrazione separati dagli unit test e non dovrebbero essere eseguiti insieme agli unit test. Possiamo farlo utilizzando un profilo diverso per eseguire solo i test di integrazione. Un paio di ragioni per fare ciò potrebbero essere che i test di integrazione richiedono molto tempo e potrebbero richiedere un database effettivo per l'esecuzione.

Tuttavia in questo articolo non ci concentreremo su questo e utilizzeremo invece l'archiviazione di persistenza H2 in memoria.

I test di integrazione devono avviare un contenitore per eseguire i casi di test. Quindi, è necessaria una configurazione aggiuntiva per questo: tutto questo è facile in Spring Boot:

@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 }

L' annotazione @SpringBootTest è utile quando è necessario eseguire il bootstrap dell'intero contenitore. L'annotazione funziona creando l' ApplicationContext che verrà utilizzato nei nostri test.

Possiamo usare la webEnvironment attributo del @SpringBootTest per configurare il nostro ambiente di esecuzione; stiamo usando WebEnvironment.MOCK qui in modo che il contenitore funzioni in un ambiente servlet fittizio .

Successivamente, l' annotazione @TestPropertySource aiuta a configurare le posizioni dei file delle proprietà specifici dei nostri test. Notare che il file delle proprietà caricato con @TestPropertySource sovrascriverà il file application.properties esistente .

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.

Il codice sorgente completo di questo articolo può essere trovato su GitHub. Il codice sorgente contiene molti altri esempi e vari casi di test.

E se vuoi continuare a imparare sui test, abbiamo articoli separati relativi ai test di integrazione e agli unit test in JUnit 5.