Teste de Controllers são aqueles que validam o comportamento do gerenciamento entre as camadas de modelo(Model) e visão(View), com isso, essa camada não deve testar regra de negócio, e sim conversões de formatos do model para view, disponibilização de API, consumo de API, entre outros. Pensando nisso, o Spring Boot disponibiliza algumas anotações para facilitar os testes dessa camada, onde visa simplificar o isolamento da camada de controller em relação as outras camadas, assim possibilitando o teste concentrando nessa camada.
Para utilizar os recursos de testes no Spring Boot, é necessário adicionar a dependência:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>1.5.2.RELEASE</version> <scope>test</scope> </dependency>
A partir da versão 1.4.0 do Spring Boot surgiram algumas anotações como @WebMvcTest e @AutoConfigureMockMvc visando facilitar a configuração para a realização de testes de controllers.
import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @WebMvcTest(HelloController.class) public class HelloControllerTest { @Autowired private MockMvc mockMvc; @Test public void hello() throws Exception { mockMvc.perform(get("/hello") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string("Hello")); } }
No entanto, caso o projeto estiver utilizando JPA, como por exemplo a anotação @EnableJpaRepositories do Spring data, ocorre um conflito de configuração quando utilizando a anotação @WebMvcTest impedindo o funcionamento do teste, pois essa anotação desabilita o funcionamento do JPA.
Exemplo do erro ocasionado pela anotação @EnableJpaRepositories
Caused by: java.lang.IllegalArgumentException: At least one JPA metamodel must be present! at org.springframework.util.Assert.notEmpty(Assert.java:276) at org.springframework.data.jpa.mapping.JpaMetamodelMappingContext.&amp;lt;init&amp;gt;(JpaMetamodelMappingContext.java:52) at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:71) at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:26) at org.springframework.beans.factory.config.AbstractFactoryBean.afterPropertiesSet(AbstractFactoryBean.java:134) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1642) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1579)
Exemplo da configuração do Spring Boot que ocasionou o erro no momento do teste
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication @ComponentScan(basePackageClasses = {HelloService.class, HelloController.class}) @EnableJpaRepositories(basePackageClasses = {HelloRepository.class}) public class AppConfig { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(AppConfig.class, args); } }
Observação: Esse problema citado acima ocorreu nas versão 1.4.0 e 1.5.2 do Spring Boot.
Com isso, quando houver configurações de JPA, como no exemplo a cima, é possível testar os controllers utilizando as anotações @SpringBootTest e @AutoConfigureMockMvc, como demonstrado a baixo:
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc public class HelloControllerTest { @Autowired private MockMvc mockMvc; @Test public void hello() throws Exception { mockMvc.perform(get("/hello") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string("Hello")); } }
Durante os testes de controller não deve-se utilizar a camada de dados, com isso, caso o Controller a ser testado tenha alguma dependência dessa camada é possível mocar o comportamento dessas camadas, segue o exemplo de como mocar o funcionamento do serviço:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc public class HelloControllerTest { @Autowired private MockMvc mockMvc; @MockBean private HelloService helloService; @Test public void listar() throws Exception { given(helloService.listar()).willReturn(new Hello("Hello")); mockMvc.perform(get("/hello/listar") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string("Hello")); } }
Caso esteja utilizando uma versão anterior a 1.4.0 do Spring Boot é possível testar controller em conjunto com o Mockito, como no exemplo a seguir:
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.MockitoAnnotations; import org.springframework.boot.SpringBootConfiguration; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootConfiguration public class HelloControllerTest { private MockMvc mockMvc; @InjectMocks private HelloController helloController; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mockMvc = MockMvcBuilders.standaloneSetup(helloController).build(); } @Test public void hello() throws Exception { mockMvc.perform(get("/hello") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string("Hello")); } }
Diferente das versões mais atuais do Spring Boot, é preciso utilizar o Mockito para injetar o controller no contexto do teste e posteriormente inicializar o mockMvc através de builders do Spring.