Testes Unitários de Controladores Web: Configuração
Ao concluirmos esta aula, nós:
- Saberemos como selecionar criteriosamente apenas os componentes necessários para testar um controlador web.
- Compreenderemos como criar e incluir componentes adicionais (
SimpleMappingExceptionResolver,FixedLocaleResolver,ViewResolvereStaticMessageSource) no Sistema Sob Teste usandoWebTestConfigeStandaloneMockMvcBuilder.
[!NOTE] Esta aula assume que:
De Quais Componentes Nós Realmente Precisamos?
Quando utilizamos a configuração standalone, podemos selecionar quais componentes queremos incluir no Sistema Sob Teste através da API da classe StandaloneMockMvcBuilder. Essa grande flexibilidade é uma faca de dois gumes e pode causar dois problemas se não tomarmos cuidado:
- Primeiro, configuração enganosa: Se incluirmos um componente extra no Sistema Sob Teste, qualquer pessoa que leia nosso código de teste assumirá que aquele componente é exigido pelos casos de teste daquela classe. Se ele não for necessário ali, nossa configuração se torna enganosa e dificulta a leitura (fazendo o desenvolvedor perder tempo precioso investigando componentes irrelevantes quando um teste falha).
- Segundo, testes pesados e difíceis de manter: Como o
StandaloneMockMvcBuilderpermite configurar praticamente qualquer componente do Spring MVC, pode ser tentador pendurar tudo nele por padrão. Se fizermos isso, nossos testes deixarão de ser simples testes unitários e ficarão difíceis de manter sempre que a configuração global mudar.
Por isso, devemos minimizar o número de componentes customizados incluídos no Sistema Sob Teste e não devemos copiar cegamente a mesma configuração para todas as classes de teste, pois diferentes controladores têm necessidades diferentes!
Aqui está a lista enxuta de componentes tipicamente utilizados ao escrever testes unitários para controladores Spring MVC tradicionais (aqueles que processam requisições HTTP renderizando uma view ou redirecionando):
- Configuramos o
HandlerExceptionResolver(ExceptionResolver) se nossa aplicação renderiza páginas de erro específicas quando um método do controlador lança uma exceção. - Especificamos o
LocaleResolverse um objetoLocalefor injetado como parâmetro em algum método do controlador testado. - Especificamos o
ViewResolvercaso não queiramos usar o resolvedor interno padrão do Spring MVC Test (por exemplo, para evitar erro de referência circular quando o nome da view coincide com a URL da requisição).
[!NOTE] Nota Importante sobre o
ViewResolver: Na maioria das vezes, escrevemos asserções apenas para o nome lógico da view retornada pelo controlador (ex.:"task/list"), mas configurar umViewResolverexplícito nostandaloneSetupgarante que oMockMvcsaiba diferenciar perfeitamente o caminho da view da URL da requisição.
Vamos descobrir agora como criar e configurar esses três componentes utilizando métodos fábrica na nossa classe WebTestConfig!
Criando os Componentes Necessários em WebTestConfig
Criamos os componentes necessários adicionando três métodos fábrica ao nosso objeto utilitário WebTestConfig:
exceptionResolver(): Cria e retorna um objetoSimpleMappingExceptionResolverque seleciona a view de erro renderizada com base no tipo da exceção lançada (TaskNotFoundException->"error/404",Exception/RuntimeException->"error/error") e define o código de status HTTP retornado quando cada view de erro é renderizada (404e500).fixedLocaleResolver(): Retorna um novo objetoFixedLocaleResolverque sempre retornaLocale.ENGLISH. Usar umLocalefixo em testes é uma excelente prática porque garante que o idioma do sistema operacional da máquina onde os testes estão rodando jamais cause falsos positivos!jspViewResolver()(ouviewResolver()): Cria um objetoInternalResourceViewResolverconfigurando prefixo e sufixo de resolução de views.
Depois de adicionarmos esses três métodos fábrica à nossa classe Object Mother WebTestConfig, seu código-fonte completo em Kotlin fica assim:
|
|
Agora vamos descobrir como incluir esses componentes na configuração do nosso Sistema Sob Teste!
Configurando o Sistema Sob Teste (TaskCrudControllerTest)
Antes de configurarmos o Sistema Sob Teste usando a configuração standalone, precisamos identificar as dependências do controlador que será testado.
Suponha que estejamos escrevendo testes unitários para a classe TaskCrudController, cujo código-fonte estrutural é o seguinte:
|
|
Como podemos ver pelo construtor, esse controlador possui duas dependências:
- O objeto
TaskCrudService, que fornece as operações CRUD para tarefas. - O objeto
MessageSource, que é utilizado para buscar mensagens internacionalizadas de feedback exibidas ao usuário.
Depois de identificarmos as dependências do controlador testado, precisamos criá-las no método de setup (@BeforeEach) que é executado antes de cada método de teste seguindo estes passos:
- Adicionamos as propriedades
crudService: TaskCrudServiceemessageSource: StaticMessageSourceà nossa classe de teste. - Adicionamos o método
@BeforeEach fun configureSystemUnderTest()à nossa classe de teste. - Criamos um novo mock de
TaskCrudService(mockk(relaxed = true)) e atribuímos à propriedadecrudService. - Criamos uma nova instância de
StaticMessageSourcee atribuímos à propriedademessageSource. Utilizamos a classeStaticMessageSource(fornecida pelo próprio Spring Framework!) porque ela é um Fake em memória oficial doMessageSourceque nos permite registrar mensagens programaticamente commessageSource.addMessage(...)— o que é muito mais limpo do que fazer stubbing manual de métodos sobrecarregados deMessageSource!
Após criarmos as dependências do controlador testado, o código-fonte da nossa classe de teste fica assim:
|
|
Nosso próximo passo é instanciar o TaskCrudController, criar o objeto MockMvc e registrar os componentes de WebTestConfig que são exigidos pelos testes desse controlador:
- Incluímos o
jspViewResolver()via.setViewResolvers(...). - Incluímos o
exceptionResolver()via.setHandlerExceptionResolvers(...)porque queremos garantir que a view de erro correta (error/404) seja renderizada e o status HTTP404seja retornado quando um método do controlador lançarTaskNotFoundException. - Incluímos o
fixedLocaleResolver()via.setLocaleResolver(...)porque um objetoLocaleé injetado como parâmetro em alguns métodos do controlador para buscar mensagens noMessageSource.
Depois de criarmos o objeto MockMvc e incluirmos esses três componentes no Sistema Sob Teste, o código-fonte completo da configuração de TaskCrudControllerTest fica assim:
|
|
Resumo
Nesta aula, aprendemos dois pontos essenciais:
- Devemos minimizar o número de componentes customizados na configuração do
MockMvce registrar apenas aqueles que são efetivamente utilizados pelo controlador sob teste. - Incluímos componentes customizados (
HandlerExceptionResolver,LocaleResolver,ViewResolver) no Sistema Sob Teste utilizando os métodos fluentes da classeStandaloneMockMvcBuilderalimentados pela nossa classe utilitáriaWebTestConfig.