Testando serviços REST
Uma pergunta frequente em muitos projetos é qual tipo de testes devo fazer? A resposta, como quase tudo em nossa área, não é única, e cada caso deve ser analisado separadamente, sendo um específico o de aplicações WEB e serviços REST.
Testes de sistema em aplicações WEB tradicionais são usados para garantir todo o fluxo de uso de uma aplicação: desde o usuário abrindo a página inicial até os cliques na interface apresentada no browser. Mas no caso de um serviço REST, o cliente é uma outra aplicação, o código de programação de um cliente.
Por exemplo, imagine o serviço REST que traz as informações de um carrinho fazendo uma requisição GET para http://www.meuservidor.com/carrinhos/2378634:
GET /carrinhos/2378634 HTTP/1.1 Host: www.meuservidor.com
Trazendo o resultado:
<carrinho> <produtos> <produto> <id>6237</id> <quantidade>1</quantidade> <nome>Videogame 4</nome> <preco>4000</preco> </produto> <produto> <id>3467</id> <quantidade>2</quantidade> <nome>Jogo de esporte</nome> <preco>120</preco> </produto> </produtos> <total>4120</total> <entrega> <rua>Rua Vergueiro 3185, 8 andar</rua> <cidade>São Paulo</cidade> </entrega> </carrinho>
Como escrever um teste para isso? Primeiro desejamos efetuar a requisição GET de verdade, isto é, criar um cliente web, como um navegador, que faça o GET e traga o xml que é o resultado esperado. Em Java temos a API HttpClient do Apache que permite fazer tais requisições, mas desde a última versão do JAX-RS temos uma API cliente ainda mais avançada, que vamos utilizar. Começamos criando um cliente HTTP:
Client client = ClientBuilder.newClient();
Precisamos de um cliente pois ele funciona como um navegador: diversas requisições efetuadas pelo mesmo cliente compartilham os mesmos cookies etc, portanto não pretendemos criar um cliente por requisição. O nosso servidor alvo é ‘www.meuservidor.com’:
WebTarget target = client.target("http://www.meuservidor.com");
E pretendemos acessar a URI ‘/carrinhos/’:
String resultado = target.path("/carrinhos/2378634").request().get(String.class);
Agora podemos conferir que o XML retornado contem aquilo que desejamos:
Assert.assertEquals("Rua Vergueiro 3185, 8 andar", resultado);
Claro que o código completo acessa a URI desejada, faz a requisição e confere o resultado, mas ela não é necessariamente perfeita: ela só conferiu que parte da String está contida. Talvez o seu teste de sistema queira conferir que o resultado retornado era o esperado. O JAX-RS utiliza por padrão a serialização do JAXB, padrão oficial de serialização do Java. Portanto podemos pedir para ele deserializar o conteúdo utilizando o JAXB para um Carrinho, e então conferir sua rua:
Carrinho carrinho = (Carrinho) target.path("/carrinhos/1").request().get(Carrinho.class); Assert.assertEquals("Rua Vergueiro 3185, 8 andar",carrinho.getRua());
Com isso temos um teste completo que acessa uma URI via GET, retorna o dado deserializado e garante o valor que estamos esperando. Podemos utilizar o resto da API cliente do JAX-RS para fazer diversos outros tipos de requisição, interagindo com o servidor, alterando o estado do mesmo e confirmando os resultados.
Mas para executar todos esses testes você precisa ter seu servidor ligado… e aí entra um problema antigo: posso levantar meu Tomcat, meu Jetty, meu servidor de aplicação ou qualquer outra aplicação, como desenvolvedor, mas os testes podem se misturar com o que estou fazendo no navegador e quebrarem. Por exemplo, se eu começo a interagir com o servidor enquanto os testes rodam, posso quebrar o teste.
O que fazer? Levantar um servidor só para o teste e só enquanto o teste é executado. No caso do Jersey (implementação padrão do JAX-RS), podemos fazer isso com o Grizzly antes de todo teste:
ResourceConfig config = new ResourceConfig().packages("br.com.alura.loja"); URI uri = URI.create("http://localhost:7575/"); HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri, config);
E derrubá-lo após cada teste:
server.stop();
Pronto! A cada teste nosso servidor é levantado em uma porta específica, que não atrapalha meu desenvolvimento, o mesmo é executado, e o servidor é derrubado. Já existem diversas bibliotecas que facilitam esse trabalho de levantar servidores para rodar testes end-to-end, desde o antigo Cargo, até a API proprietária de cada servidor, como neste caso o Grizzly.
Você pode conhecer mais sobre testes end-to-end com sistemas REST, inclusive como navegar entre recursos no curso online do Alura.
Programação, Mobile, Front-end, Design & UX, Infraestrutura e Business
Muito bom o post, Guilherme.
Normalmente eu peço para o Spring levantar um Jetty embarcado com o servlet do CXF para WS-Soap ou mesmo levantar todo o contexto da aplicação para WS-REST com VRaptor3. Algumas vezes já levantei um servidor HTTP embarcado para testar o cliente do WS e não serviço acessado – a própria SUN/Oracle tem um HttpServer na embarcado na JavaSE que podemos usar.
Tenho uma dúvida em relação ao Grizzly, quando você levantou o servidor, como ele soube quais serviços REST da aplicação levantar? Foi através do
new ResourceConfig().packages("br.com.alura.loja")
? Nesse caso, ele só levanta serviços do JAX-RS?Gui, excelente! Assunto que me interessa muito e não tinha achado muita coisa a respeito.
Dúvidas…
– o Grizzly espera um war?
– Vc colocaria os testes dos serviços REST dentro do mesmo projeto ou em projetos separados?
Não sei como funciona o Grizzly, mas creio que ele levante um servidor embarcado certo? E isso impactaria no tempo de build da aplicação.
– Nesse “war” do grizzly também teria que apontar para um BD diferente.
Posso ter entendido errado, mas vc subiu em outro servidor a aplicação, mas se elas ainda usarem o mesmo banco, os testes do REST ainda serão impactados pelo o que for feito por quem tiver utilizando o navegador (considerando que os testes rodem em um servidor de integração contínua)
Enfim, muitas dúvidas! hahhahaha
Post muito interessante, parabéns Guilherme.
Eu dei uma estudada superficial mas lembro que achei bem interessante e era simples.
Fugindo um pouco do escopo do post, ja utilizei bastante o arquillian pra rodar os diversos testes, não apenas dos serviços REST. Tem diversas features legais e ele mesmo sobe pra você uma instancia do servidor ou roda embarcado, ao finalizar os testes ele derruba.
Eu costumo utilizar os “embedded servers” para fazer os testes de integração. Utilizando maven fica bem fácil configurar para que antes de iniciar os testes de integração, o próprio maven suba seu embedded server(tomcat, jetty, whatever) e após isso rode sua suite de testes.
Para fazer as chamadas http e os asserts das respostas costumo utilizar o Rest-Assured(http://code.google.com/p/rest-assured/) ele disponibiliza uma DSL bem simples e fácil para fazer as chamadas e verificar os resultados. E o melhor, ele é bem voltado para REST, com uma DSL muito boa para representar as requisições get, post, delete, e etc.
Genial, Guilherme.
Essa deserialização do JAXB é uma mão na roda.
O Jersey está a cada dia me deixando mais confortável em criar WS REST, todos com testes unitários. Dá pra perceber a qualidade e a maturidade que a API desenvolvida vai tomando quando você escreve esse tipo de teste antes de desenvolver…
Ótimo post!
Olá, interessante o post.
Um bom complemento é getting started do jersey: https://jersey.java.net/documentation/latest/getting-started.html.
Ele explica bem a questão do Grizzly e outras coisas.
Ótimo post como todos comentaram!
Só passei para deixar que o RESTEasy também tem um meio de fazer testes sem subir um servidor externo, vejam esse exemplo:
https://github.com/resteasy/Resteasy/blob/master/jaxrs/resteasy-jaxrs/src/test/java/org/jboss/resteasy/test/security/BasicAuthTest.java
[]’s
Isso mesmo Pontes, quando falei o pacote ele já buscou os que estavam lá. Se não me engano isso é uma extensão do Jersey com o Grizzly e não do Grizzly.
Rapha, você quer ter certeza que a app subiu para então continuar né? Sim, da maneira que faço o código é blocante até sua app levantar (não um war no exemplo dado). Sobre o problema que citou, isso é um problema de configuração de ambientes, ambiente de dev/tests/acceptance/production/etc. Usar o vraptor-environment resolve (nativo no vraptor4), da uma olhadinha nele! Voce pega .getResource(“hibernate.cfg.xml”) e ele pega um diferente para cada environment.
Abraço!
Muito bom Guilherme, voce indica algum material pra iniciar com rest java? estou buscando padronizar os métodos mais próximos de como o mercado trabalha.
Boa tarde Diogo,
O http://www.alura.com.br oferece um curso de JAX-RS e introdução a Rest com Jersey, além de um de VRaptor.
Já na Caelum temos cursos que oferecem REST, SOA e mais.
Abraço!