Solucionado (ver solução)
Solucionado
(ver solução)
6
respostas

Como fazer SpringBoot Test com Spring Security e Mock Repository

Estou tentando realizar um teste de integração em minha API que utiliza spring mvc, spring security, spring data, porem só obtive sucesso realizando ela com contexto como mostra o exemplo abaixo.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PaisControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext context;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders
          .webAppContextSetup(context)
          .apply(springSecurity())
          .build();
    }

    @WithMockUser(username="ADMIN")
    @Test
    public void deveListarTodosPaises() throws Exception{            
        mockMvc.perform(get("/paises")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk());
    }    
}

Dessa maneira consegui testar perfeitamente, porem ele sempre sobre a aplicação completa o que leva um tempo considerável, ainda mais quando existirem mais testes para serem executados em conjunto.

Gostaria de saber se existe uma forma de se fazer isso de maneira mais rápida, sem precisar ter que subir a aplicação por completa?

Li a respeito da anotação @WebMvcTest porem não obtive sucesso com os testes que fiz sempre gerando erros com o security, alguém tem algum exemplo ou existe algum curso na Alura que trate a respeito de testes com SprintBoot Test?

6 respostas

Aplicando mais uns testes e fazendo tudo que usava na minha validacao do token por spring security virar @MockBean acabei conseguindo fazer rodar o teste com @WebMvcTest com o codigo abaixo:

@RunWith(SpringRunner.class)
@WebMvcTest(PaisController.class)
@ContextConfiguration(classes={ProjetoApplication.class, SecurityConfiguration.class})
public class PaisControllerMockTest {

    @Autowired
    private MockMvc mockMvc;    

    @MockBean
    private UsuarioService usuarioService;    

    @MockBean
    private TokenService tokenService;    

    @MockBean
    private UsuarioRepository usuarioRepository;   

    @MockBean
    private PaisService paisService;    

    @WithMockUser(username="ADMIN")
    @Test
    public void deveListarTodosPaises() throws Exception {

        mockMvc.perform(get("/paises/1")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}

Mas não queria que mockar o PaisService, gostaria apenas o PaisRepository usado dentro dele, pois as minhas regras de negocio estão dentro do @Service.

Se eu tiro o @MockeBean do servico e crio um com repository esta gerando o erro a seguir:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'paisController': Unsatisfied dependency expressed through field 'service'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'br.com.projeto.services.PaisService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Alguém já tentou aplicar esse tipo teste sem precisar mocar o service, apenas o repository ?

Deixa eu atualizar o tópico que acabei na madrugada achando a anotação @ContextConfiguration que consegue subir o contexto com tudo que utilizar @Repository, @Componente, @Service, sendo assim consegui fazer a classe com mockup no repository.

@RunWith(SpringRunner.class)
@WebMvcTest(PaisController.class)
@ContextConfiguration(classes={ProjetoApplication.class, 
                               TokenService.class, 
                               SecurityConfiguration.class,                                
                               PaisService.class})
public class PaisControllerMockTest {

    @Autowired
    private MockMvc mockMvc;    

    @MockBean
    UsuarioService usuarioService;

    @MockBean
    private UsuarioRepository usuarioRepository;   

    @MockBean
    private PaisRepository paisRepository;

    @WithMockUser(username="ADMIN")
    @Test
    public void deveListarUmPais() throws Exception {
        Pais pais = new Pais();
        pais.setId(1);
        pais.setNome("Brasil");

        when(paisRepository.findById(1)).thenReturn(Optional.of(pais));

        mockMvc.perform(get("/paises/1")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}

Ai fiquei somente com a duvida se essa é a melhor maneira de realmente fazer os testes de integração e como eu conseguiria funcionar também as interfaces JPARepository dentro do @ContextConfiguration?

Antes que alguém diga , eu ja tentei colocar @Repository e mesmo assim ele não aceita.

Utilizar @SpringBootTest geralmente é a melhor forma de realizar esses testes de integração, caso contrário você teria que mockar várias camadas, o que é mais um teste unitário.

ola Otavio, mas e como vocês acabam lidando com a lentidão desse tipo de teste? Exista alguma forma de deixar ele mais rápido? Pergunto isso porque imagino testar dezenas de controllers em um sistema, isso vai demorar bastante se para cada controller tiver que baixar e subir todo o contexto, para economizar tempo e recurso, existe alguma maneira de subi uma unica vez o contexto e realizar todos os testes possíveis ? (tirando a opção de existir uma unica classe teste para todo o sistema com todos os @Test juntos).

O spring só irá subir novamente caso algum teste esteja "sujando" o contexto, algum teste seu está usando @DirtiesContext ou @MockBean?

Dá uma olhada nesse artigo, tem bastante informação útil: https://www.baeldung.com/spring-tests

solução!

muito obrigado Otávio, deixa como sugestão para a curadoria criar um novo curso de spring REST parte 03 focando nessa área de testes ou então dar uma revisada nos que existem hoje, esse tipo de conteúdo na alura ta em falta e é muito exigido no dia a dia.