Testando um serviço da Web JAX-RS?

84

Atualmente, estou procurando maneiras de criar testes automatizados para um serviço da Web baseado em JAX-RS (Java API para RESTful Web Services).

Basicamente, preciso enviar certas entradas e verificar se obtenho as respostas esperadas. Eu prefiro fazer isso por meio do JUnit, mas não tenho certeza de como isso pode ser feito.

Que abordagem você usa para testar seus serviços da web?

Atualização: como entzik apontou, separar o serviço da web da lógica de negócios me permite testar a lógica de negócios de unidade. No entanto, também quero testar os códigos de status HTTP corretos, etc.

Einar
fonte
6
Boa pergunta - no entanto, eu diria que se você está testando em HTTP, então me parece que este é um teste de integração.
Tom Duckering
Tom. Você está absolutamente certo. Devemos injetar um emulador HTTP fictício / contêiner leve para isso. Em node.js, o superteste mundial faz isso. Você pode emular express.js.
Fırat KÜÇÜK

Respostas:

34

Jersey vem com uma excelente API de cliente RESTful que torna a escrita de testes de unidade realmente fácil. Consulte os testes de unidade nos exemplos fornecidos com o Jersey. Usamos essa abordagem para testar o suporte REST no Apache Camel , se você estiver interessado, os casos de teste estão aqui

James Strachan
fonte
6
re: agora link inválido Você pode encontrar os exemplos mencionados na camisa / amostras que mostram testes de unidade, basicamente usando consumidores de camisa para consumir recursos da web. download.java.net/maven/2/com/sun/jersey/samples/bookstore/…
rogerdpack
2
Este projeto está no GitHub, encontre os testes na pasta src / test: github.com/jersey/jersey/tree/master/examples/bookstore-webapp
Venkat
2
Não duvido dessa resposta, mas acho incrivelmente engraçado que Jersey sempre faça seu caminho para uma conversa JAX-RS, quando em alguns casos (WebSphere, infelizmente, para ser exato) está indisponível e renderiza 99% de todas as respostas aceitáveis no Stack Overflow nulo e vazio.
26

Você pode experimentar REST Assured, que torna muito simples testar serviços REST e validar a resposta em Java (usando JUnit ou TestNG).

João
fonte
1
Votei no seu post porque a biblioteca parecia boa, mas eles com certeza usam muitos potes dependentes ...
Perry Tew
17

Como James disse; Há uma estrutura de teste integrada para Jersey. Um exemplo simples de hello world pode ser assim:

pom.xml para integração maven. Quando você corre mvn test. Frameworks iniciam um contêiner grizzly. Você pode usar jetty ou tomcat alterando dependências.

...
<dependencies>
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.16</version>
  </dependency>

  <dependency>
    <groupId>org.glassfish.jersey.test-framework</groupId>
    <artifactId>jersey-test-framework-core</artifactId>
    <version>2.16</version>
    <scope>test</scope>
  </dependency>

  <dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>2.16</version>
    <scope>test</scope>
  </dependency>
</dependencies>
...

ExampleApp.java

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class ExampleApp extends Application {

}

HelloWorld.java

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/")
public final class HelloWorld {

    @GET
    @Path("/hello")
    @Produces(MediaType.TEXT_PLAIN)
    public String sayHelloWorld() {

        return "Hello World!";
    }
}

HelloWorldTest.java

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.core.Application;
import static org.junit.Assert.assertEquals;

public class HelloWorldTest extends JerseyTest {

    @Test
    public void testSayHello() {

        final String hello = target("hello").request().get(String.class);

        assertEquals("Hello World!", hello);
    }

    @Override
    protected Application configure() {

        return new ResourceConfig(HelloWorld.class);
    }
}

Você pode verificar este aplicativo de amostra.

Fırat KÜÇÜK
fonte
Com o Jersey 2.29.1, tive que adicionar jersey-hk2como uma dependência porque estava recebendo um java.lang.IllegalStateException: InjectionManagerFactoryerro não encontrado (veja esta pergunta ). Caso contrário, este exemplo funciona bem.
Sarah N
7

Você provavelmente escreveu algum código java que implementa sua lógica de negócios e, em seguida, gerou o ponto de extremidade de serviços da web para ele.

Uma coisa importante a fazer é testar independentemente sua lógica de negócios. Como é código Java puro, você pode fazer isso com testes JUnit regulares.

Agora, como a parte dos serviços da web é apenas um ponto final, o que você deseja ter certeza é que o encanamento gerado (stubs, etc) está em sincronia com seu código java. você pode fazer isso escrevendo testes JUnit que invocam os clientes java de serviço da web gerados. Isso permitirá que você saiba quando alterar suas assinaturas java sem atualizar o material de serviços da web.

Se o encanamento de seus serviços da web for gerado automaticamente por seu sistema de compilação a cada compilação, pode não ser necessário testar os terminais (presumindo que tudo seja gerado corretamente). Depende do seu nível de paranóia.

entzik
fonte
2
Você está certo, embora eu também precise testar as respostas HTTP reais que são retornadas, em particular os códigos de status HTTP.
Einar de
6

Embora seja muito tarde para a data de postagem da pergunta, pensei que isso possa ser útil para outras pessoas que tenham uma pergunta semelhante. Jersey vem com uma estrutura de teste chamada Jersey Test Framework, que permite testar seu serviço da Web RESTful, incluindo os códigos de status de resposta. Você pode usá-lo para executar seus testes em contêineres leves como Grizzly, HTTPServer e / ou EmbeddedGlassFish. Além disso, a estrutura pode ser usada para executar seus testes em um contêiner da web regular, como GlassFish ou Tomcat.

Bill o lagarto
fonte
Você tem um bom exemplo de como simular manipuladores de chamadas? JerseyHttpCall -> MyResource -> CallHandler.getSomething () Como podemos simular CallHandler aqui?
Balaji Boggaram Ramanarayan
3

Eu uso o HTTPClient do Apache (http://hc.apache.org/) para chamar o Restful Services. A biblioteca do cliente HTTP permite que você execute facilmente get, post ou qualquer outra operação necessária. Se o seu serviço usa JAXB para ligação xml, você pode criar um JAXBContext para serializar e desserializar entradas e saídas da solicitação HTTP.

Chris Dail
fonte
3

Dê uma olhada no gerador de cliente de descanso Alchemy . Isso pode gerar uma implementação de proxy para sua classe de serviço da web JAX-RS usando o cliente jersey nos bastidores. Efetivamente, você chamará métodos de serviço da web como métodos java simples de seus testes de unidade. Lida com autenticação http também.

Não há geração de código envolvida se você precisar simplesmente executar testes para que seja conveniente.

Isenção de responsabilidade: eu sou o autor desta biblioteca.

Ashish Shinde
fonte
2

Mantenha simples. Dê uma olhada em https://github.com/valid4j/http-matchers que pode ser importado do Maven Central.

    <dependency>
        <groupId>org.valid4j</groupId>
        <artifactId>http-matchers</artifactId>
        <version>1.0</version>
    </dependency>

Exemplo de uso:

// Statically import the library entry point:
import static org.valid4j.matchers.http.HttpResponseMatchers.*;

// Invoke your web service using plain JAX-RS. E.g:
Client client = ClientBuilder.newClient();
Response response = client.target("http://example.org/hello").request("text/plain").get();

// Verify the response
assertThat(response, hasStatus(Status.OK));
assertThat(response, hasHeader("Content-Encoding", equalTo("gzip")));
assertThat(response, hasEntity(equalTo("content")));
// etc...
keyoxy
fonte
1

Uma coisa importante a fazer é testar independentemente sua lógica de negócios

Eu certamente não presumiria que a pessoa que escreveu o código JAX-RS e está procurando fazer o teste de unidade da interface esteja de alguma forma, por algum motivo bizarro e inexplicável, alheio à noção de que ele ou ela pode testar a unidade de outras partes do programa, incluindo classes de lógica de negócios. Não ajuda muito afirmar o óbvio, e foi dito repetidamente que as respostas também precisam ser testadas.

Tanto Jersey quanto RESTEasy têm aplicativos clientes e, no caso do RESTEasy, você pode usar as mesmas anotações (até mesmo fatorar a interface anotada e usar no cliente e no servidor de seus testes).

DESCANSE não o que este serviço pode fazer por você; DESCANSE o que você pode fazer por este serviço.


fonte
As pessoas podem querer testar algumas questões transversais. Por exemplo, validação, autenticação, cabeçalhos HTTP desejados, etc. Portanto, as pessoas podem preferir testar seu código JAX-RS.
Fırat KÜÇÜK
Em meu aplicativo, eu uso ModelMapper para "mapear" classes de "DTO" para classes de "objetos de negócios", que são compreendidas pelas classes de "serviço" subjacentes. Este é um exemplo de algo que seria bom testar de forma independente.
jkerak
E às vezes o miniaplicativo REST tem tão pouca complexidade que os mocks seriam maiores do que a camada do aplicativo, como no meu caso atual. :)
tekHedd
1

Pelo que entendi, o principal objetivo do autor desta edição é desacoplar a camada JAX RS do negócio. E teste de unidade apenas o primeiro. Temos que resolver dois problemas básicos aqui:

  1. Execute em teste algum servidor web / aplicativo, coloque componentes JAX RS nele. E só eles.
  2. Simulação de serviços de negócios dentro de componentes JAX RS / camada REST.

O primeiro é resolvido com Arquillian. O segundo é perfeitamente descrito em arquillican e mock

Aqui está um exemplo do código. Ele pode ser diferente se você usar outro servidor de aplicativos, mas espero que você tenha uma ideia básica e vantagens.

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import com.brandmaker.skinning.service.SomeBean;

/**
* Created by alexandr on 31.07.15.
*/
@Path("/entities")
public class RestBean
{
   @Inject
   SomeBean bean;

   @GET
   public String getEntiry()
   {
       return bean.methodToBeMoked();
   }
}

import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

import com.google.common.collect.Sets;

/**
*/
@ApplicationPath("res")
public class JAXRSConfiguration extends Application
{
   @Override
   public Set<Class<?>> getClasses()
   {
       return Sets.newHashSet(RestBean.class);
   }
}


public class SomeBean
{
   public String methodToBeMoked()
   {
       return "Original";
   }
}

import javax.enterprise.inject.Specializes;

import com.brandmaker.skinning.service.SomeBean;

/**
*/
@Specializes
public class SomeBeanMock extends SomeBean
{
   @Override
   public String methodToBeMoked()
   {
       return "Mocked";
   }
}

@RunWith(Arquillian.class)
public class RestBeanTest
{
   @Deployment
   public static WebArchive createDeployment() {
       WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war")
               .addClasses(JAXRSConfiguration.class, RestBean.class, SomeBean.class, SomeBeanMock.class)
               .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
       System.out.println(war.toString(true));
       return war;
   }

   @Test
   public void should_create_greeting() {
       Client client = ClientBuilder.newClient();
       WebTarget target = client.target("http://127.0.0.1:8181/test/res/entities");
       //Building the request i.e a GET request to the RESTful Webservice defined
       //by the URI in the WebTarget instance.
       Invocation invocation = target.request().buildGet();
       //Invoking the request to the RESTful API and capturing the Response.
       Response response = invocation.invoke();
       //As we know that this RESTful Webserivce returns the XML data which can be unmarshalled
       //into the instance of Books by using JAXB.
       Assert.assertEquals("Mocked", response.readEntity(String.class));
   }
}

Algumas notas:

  1. A configuração JAX RS sem web.xml é usada aqui.
  2. O cliente JAX RS é usado aqui (sem RESTEasy / Jersey, eles expõem uma API mais conveniente)
  3. Quando o teste começa, o runner do Arquillian começa a funcionar. Aqui você pode descobrir como configurar testes para Arquillian com o servidor de aplicação necessário.
  4. Dependendo do servidor de aplicativos escolhido, um url no teste será um pouco diferente. Outra porta pode ser usada. 8181 é usado por Glassfish Embedded em meu exemplo.

Espero que ajude.

Alexandr
fonte
esse tipo de coisa é compatível com a estrutura de teste de jersey integrada? Estou apenas hesitante em adicionar "mais uma estrutura" e todos os seus frascos ao meu projeto se já tiver algo disponível em Jersey.
jkerak