Jersey – Come prendere in giro il servizio

Sto usando “Jersey Test Framework” per l’unità testare il mio webservice.

Ecco la mia class di risorse:

import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; // The Java class will be hosted at the URI path "/helloworld" @Path("/helloworld") public class class HelloWorldResource { private SomeService service; @GET @Produces("text/plain") public String getClichedMessage() { // Return some cliched textual content String responseFromSomeService = service.getSomething(); return responseFromSomeService; } } 

Come posso prendere in giro SomeService nei test unitari?

Ecco come l’ho fatto con Jersey 2.20, Spring 4.1.4 RELEASE, Mockito 1.10.8 e TestNG 6.8.8.

 @Test public class CasesResourceTest extends JerseyTestNg.ContainerPerMethodTest { @Mock private CaseService caseService; @Mock private CaseConverter caseConverter; @Mock private CaseRepository caseRepository; private CasesResource casesResource; @Override protected Application configure() { MockitoAnnotations.initMocks(this); casesResource = new CasesResource(); AbstractBinder binder = new AbstractBinder() { @Override protected void configure() { bindFactory(new InstanceFactory(caseConverter)).to(CaseConverter.class); bindFactory(new InstanceFactory(caseService)).to(CaseService.class); } }; return new ResourceConfig() .register(binder) .register(casesResource) .property("contextConfigLocation", "solve-scm-rest/test-context.xml"); } public void getAllCases() throws Exception { when(caseService.getAll()).thenReturn(Lists.newArrayList(new solve.scm.domain.Case())); when(caseConverter.convertToApi(any(solve.scm.domain.Case.class))).thenReturn(new Case()); Collection cases = target("/cases").request().get(new GenericType>(){}); verify(caseService, times(1)).getAll(); verify(caseConverter, times(1)).convertToApi(any(solve.scm.domain.Case.class)); assertThat(cases).hasSize(1); } } 

È inoltre necessaria questa class che rende più semplice il codice di binding:

 public class InstanceFactory implements Factory { private T instance; public InstanceFactory(T instance) { this.instance = instance; } @Override public void dispose(T t) { } @Override public T provide() { return instance; } } 

Modificato come pr. richiesta. Questo è il contenuto del mio test-context.xml:

    

Si scopre che il mio test-context.xml non crea un’istanza di alcun bean né scansiona alcun pacchetto, infatti, non fa nulla. Credo di averlo messo lì nel caso in cui ne avessi avuto bisogno.

Vedi Aggiornamento qui sotto: non hai bisogno di una Factory


Se si utilizza Jersey 2, una soluzione sarebbe quella di utilizzare la funzione Custom Injection e Lifecycle Management (con HK2 – che viene fornito con il dist del Jersey). Anche richiesto sarebbe un quadro di derisione, naturalmente. Userò Mockito.

Per prima cosa crea una fabbrica con un’istanza derisa:

 public static interface GreetingService { public String getGreeting(String name); } public static class MockGreetingServiceFactory implements Factory { @Override public GreetingService provide() { final GreetingService mockedService = Mockito.mock(GreetingService.class); Mockito.when(mockedService.getGreeting(Mockito.anyString())) .thenAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { String name = (String)invocation.getArguments()[0]; return "Hello " + name; } }); return mockedService; } @Override public void dispose(GreetingService t) {} } 

Quindi utilizzare AbstractBinder per colbind la fabbrica all’interfaccia / class di servizio e registrare il raccoglitore. (È tutto descritto nel link sopra):

 @Override public Application configure() { AbstractBinder binder = new AbstractBinder() { @Override protected void configure() { bindFactory(MockGreetingServiceFactory.class) .to(GreetingService.class); } }; ResourceConfig config = new ResourceConfig(GreetingResource.class); config.register(binder); return config; } 

Sembra molto, ma è solo un’opzione. Non ho molta familiarità con il framework di test, o se ha una capacità mocking per l’iniezione.

Ecco il test completo:

 import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Application; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.glassfish.hk2.api.Factory; import org.glassfish.hk2.utilities.binding.AbstractBinder; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class ServiceMockingTest extends JerseyTest { @Path("/greeting") public static class GreetingResource { @Inject private GreetingService greetingService; @GET @Produces(MediaType.TEXT_PLAIN) public String getGreeting(@QueryParam("name") String name) { return greetingService.getGreeting(name); } } public static interface GreetingService { public String getGreeting(String name); } public static class MockGreetingServiceFactory implements Factory { @Override public GreetingService provide() { final GreetingService mockedService = Mockito.mock(GreetingService.class); Mockito.when(mockedService.getGreeting(Mockito.anyString())) .thenAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { String name = (String)invocation.getArguments()[0]; return "Hello " + name; } }); return mockedService; } @Override public void dispose(GreetingService t) {} } @Override public Application configure() { AbstractBinder binder = new AbstractBinder() { @Override protected void configure() { bindFactory(MockGreetingServiceFactory.class) .to(GreetingService.class); } }; ResourceConfig config = new ResourceConfig(GreetingResource.class); config.register(binder); return config; } @Test public void testMockedGreetingService() { Client client = ClientBuilder.newClient(); Response response = client.target("http://localhost:9998/greeting") .queryParam("name", "peeskillet") .request(MediaType.TEXT_PLAIN).get(); Assert.assertEquals(200, response.getStatus()); String msg = response.readEntity(String.class); Assert.assertEquals("Hello peeskillet", msg); System.out.println("Message: " + msg); response.close(); client.close(); } } 

Dipendenze per questo test:

  org.glassfish.jersey.test-framework.providers jersey-test-framework-provider-grizzly2 2.13   org.mockito mockito-all 1.9.0  

AGGIORNARE

Quindi, nella maggior parte dei casi, non hai davvero bisogno di una Factory . Puoi semplicemente associare l’istanza fittizia al suo contratto:

 @Mock private Service service; @Override public ResourceConfig configure() { MockitoAnnotations.initMocks(this); return new ResourceConfig() .register(MyResource.class) .register(new AbstractBinder() { @Override protected configure() { bind(service).to(Service.class); } }); } @Test public void test() { when(service.getSomething()).thenReturn("Something"); // test } 

Molto più semplice!