Jersey - How to mock service
Asked Answered
L

2

16

I am using "Jersey Test Framework" for unit testing my webservice.

Here is my resource class :

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;
    }
}

How can I mock SomeService in unit tests ?

Lisle answered 11/12, 2014 at 18:37 Comment(3)
Simply use a mocking framework like EasyMock or JMock. I'll try to provide an answer with example later.Sequela
I am not sure what you are trying to indicate here ? I have seen people using @Inject CDI as dependency injection and in our tests, we can use Mockito.* to mock them. But I had issues with CDI + JAX-RS Jersey. I would appreciate if you could provide an example ? ThanksLisle
What Jersey version are you using?Inexplicit
I
15

See Update below: You don't need a Factory


If you are using Jersey 2, one solution would be to use Custom Injection and Lifecycle Management feature (with HK2 - which comes with the Jersey dist). Also required would be a Mocking framework of course. I'm going to use Mockito.

First create a Factory with mocked instance:

public static interface GreetingService {
    public String getGreeting(String name);
}

public static class MockGreetingServiceFactory 
                                     implements Factory<GreetingService> {
    @Override
    public GreetingService provide() {
        final GreetingService mockedService
                = Mockito.mock(GreetingService.class);
        Mockito.when(mockedService.getGreeting(Mockito.anyString()))
                .thenAnswer(new Answer<String>() {
                    @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) {}
}

Then use the AbstractBinder to bind the factory to the interface/service class, and register the binder. (It's all described in the link above):

@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;
}

Seems like a lot, but it's just an option. I'm not too familiar with the test framework, or if it has an mocking capabilities for injection.

Here is the full test:

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<GreetingService> {
        @Override
        public GreetingService provide() {
            final GreetingService mockedService
                    = Mockito.mock(GreetingService.class);
            Mockito.when(mockedService.getGreeting(Mockito.anyString()))
                    .thenAnswer(new Answer<String>() {
                        @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();
       
    }
}

Dependencies for this test:

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>2.13</version>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.0</version>
</dependency>

UPDATE

So in most cases, you really don't need a Factory. You can simply bind the mock instance with its contract:

@Mock
private Service service;

@Override
public ResourceConfig configure() {
    MockitoAnnotations.initMocks(this);
    return new ResourceConfig()
        .register(MyResource.class)
        .register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(service).to(Service.class);
            }
        });
}

@Test
public void test() {
    when(service.getSomething()).thenReturn("Something");
    // test
}

Much simpler!

Inexplicit answered 12/12, 2014 at 15:56 Comment(10)
what is in GreetingResource.java ?Lisle
Somehow, The mocking is not successful in my case. In the unit test you wrote, its not mocking the .getGreeting method .Lisle
Look at the whole test. GreetingResource is the resource class. In the code I wrote, it is mocking the GreetingService. GreetingService is simple an interface that is injected into GreetingResource. The Factory creates the mocked instance, and the binder binds the factory to the GreetingService so that when a GreetingService is used, the mocked instance will be injected. Please run the entire test. If it wasn't mocked, then you wouldn't see any result.Inexplicit
In the test, you will see (in this order - please examine the closely) (1) GreetingResource (the Jax-RS resource class) (2) A simple interface GreetingService (that is what is being injected) (3) The Factory class, that mocks the GreetingService. (4) The application configuration where the AbstractBinder will bind the factory to the GreetingService. (5) The test. Please let me know if there is something else specific you don't understand about the test. It works fine for me, how it supposed toInexplicit
If you run the test exactly how it is right now, if the result shows as "Hello peeskillet", then that means the mocking was successful.Inexplicit
Does this also work in spring? I don't seem to go into the provide method in my own test. The example works greatDanikadanila
Only one added dependency: the mocking tool. This answer is the good one if you only use Jersey and don't want to add another tool on top of it.Magnitogorsk
Tried this but the when(...) statement had no effect on the mock. Moved the when(...) statement to the configure()-method and it worked. Not as elegant anymore...Byblow
I had to change "protected configure() {" to "public void configure()" in order for this to work.Credit
A point worth considering is, the mocks should be initialized as part of the the configure or the configureDeployment method and not method annotated with @BeforeCustodial
D
9

Here is how I did it with Jersey 2.20, Spring 4.1.4 RELEASE, Mockito 1.10.8, and 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>(caseConverter)).to(CaseConverter.class);
                bindFactory(new InstanceFactory<CaseService>(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<Case> cases = target("/cases").request().get(new GenericType<Collection<Case>>(){});

        verify(caseService, times(1)).getAll();
        verify(caseConverter, times(1)).convertToApi(any(solve.scm.domain.Case.class));

        assertThat(cases).hasSize(1);
    }
}

You also need this class which makes the binding code above a bit easier:

public class InstanceFactory<T> implements Factory<T> {

    private T instance;

    public InstanceFactory(T instance) {
        this.instance = instance;
    }

    @Override
    public void dispose(T t) {
    }

    @Override
    public T provide() {
        return instance;
    }

}

Edited as pr. request. This is the contents of my test-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

It turns out that my test-context.xml does not instantiate any beans nor scan any packages, in fact, it does not do anything at all. I guess I just put it there in case I might need it.

Disremember answered 14/8, 2015 at 11:58 Comment(3)
Elegant solution, saved me a lot of time as there are not much information about this...Ruffi
What do you have in your test-context.xml file?Garble
You could take this further and add a setter to the resource for the service and inject it that way.Bicipital

© 2022 - 2024 — McMap. All rights reserved.