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.

Tags: ,

11 Comentários

  1. Rafael Ponte 08/07/2014 at 12:56 #

    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?

  2. Raphael Lacerda 08/07/2014 at 13:30 #

    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

  3. Fred Estrela 11/07/2014 at 11:20 #

    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.

  4. Nykolas Lima 11/07/2014 at 14:52 #

    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.

  5. Vinicius Pires 14/07/2014 at 19:18 #

    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!

  6. Alessandro Lemser 21/07/2014 at 11:12 #

    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.

  7. William Antônio 13/08/2014 at 00:17 #

    Ó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

  8. Guilherme Silveira 14/08/2014 at 10:24 #

    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!

  9. Diogo 15/01/2015 at 15:12 #

    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.

  10. Guilherme Silveira 19/01/2015 at 11:57 #

    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!

Deixe uma resposta