19

There are errors when using DI in Jersey Rest application:

org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=PricingService,parent=PricingResource,qualifiers={},position=0,optional=false,self=false,unqualified=null,1633188703)

I am quite new to the concept and it appears quite complicated since there are some examples seems to be deprecated. As I understand there are a few ways to make DI work: native HK2, Spring/HK2 Bridge. What is easier and more straightforward to configure? How to set up programmatically (not a fan of XML) for Jersey 2.x?

ResourceConfig

import org.glassfish.jersey.server.ResourceConfig;

public class ApplicationConfig  extends ResourceConfig {
    public ApplicationConfig() {
        register(new ApplicationBinder());
        packages(true, "api");
    }
}

AbstractBinder

public class ApplicationBinder extends AbstractBinder {
    @Override
    protected void configure() {
        bind(PricingService.class).to(PricingService.class).in(Singleton.class);
    }
}

PricingResource

@Path("/prices")
public class PricingResource {
    private final PricingService pricingService;

    @Inject
    public PricingResource(PricingService pricingService) {
        this.pricingService = pricingService;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Collection<Price> findPrices() {
        return pricingService.findPrices();
    }
}

PricingService

@Singleton
public class PricingService {
   // no constructors...
// findPrices() ...

}

UPDATE

public class Main {
    public static final String BASE_URI = "http://localhost:8080/api/";

    public static HttpServer startServer() {
        return createHttpServerWith(new ResourceConfig().packages("api").register(JacksonFeature.class));
    }

    private static HttpServer createHttpServerWith(ResourceConfig rc) {
        HttpServer httpServer = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
        StaticHttpHandler staticHttpHandler = new StaticHttpHandler("src/main/webapp");
        staticHttpHandler.setFileCacheEnabled(false);
        staticHttpHandler.start();
        httpServer.getServerConfiguration().addHttpHandler(staticHttpHandler);
        return httpServer;
    }

    public static void main(String[] args) throws IOException {
        System.setProperty("java.util.logging.config.file", "src/main/resources/logging.properties");
        final HttpServer server = startServer();

        System.out.println(String.format("Jersey app started with WADL available at "
                + "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
        server.start();
        System.in.read();
        server.stop();
    }

}

UPDATE3:

public class PricingResourceTest extends JerseyTest {
    @Mock
    private PricingService pricingServiceMock;

    @Override
    protected Application configure() {
        MockitoAnnotations.initMocks(this);
        enable(TestProperties.LOG_TRAFFIC);
        enable(TestProperties.DUMP_ENTITY);

        ResourceConfig config = new ResourceConfig(PricingResource.class);
        config.register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(pricingServiceMock).to(PricingService.class);
            }
        });
        return config;
    }

    @Test
    public void testFindPrices(){
        when(pricingServiceMock.findPrices()).thenReturn(getMockedPrices());
        Response response  = target("/prices")
                .request()
                .get();
        verify(pricingServiceMock).findPrices();
        List<Price> prices = response.readEntity(new GenericType<List<Price>>(){});
//        assertEquals("Should return status 200", 200, response.getStatus());
        assertTrue(prices.get(0).getId() == getMockedPrices().get(0).getId());
    }

    private List<Price> getMockedPrices(){
        List<Price> mockedPrices = Arrays.asList(new Price(1L, 12.0, 50.12, 12L));
        return mockedPrices;
    }
}

JUnit output:

INFO: 1 * Client response received on thread main
1 < 200
1 < Content-Length: 4
1 < Content-Type: application/json
[{}]


java.lang.AssertionError

While debugging:

prices.get(0) is Price object that has null assigned to all fields.


UPDATE4:

Added to configure():

 config.register(JacksonFeature.class);
 config.register(JacksonJsonProvider.class);

Now Junit output a bit better:

INFO: 1 * Client response received on thread main
1 < 200
1 < Content-Length: 149
1 < Content-Type: application/json
[{"id":2,"recurringPrice":122.0,"oneTimePrice":6550.12,"recurringCount":2},{"id":2,"recurringPrice":122.0,"oneTimePrice":6550.12,"recurringCount":2}]

Indeed list prices has correct number of prices but all prices' fields is null. That leads to assumption that problem might be reading entity:

List<Price> prices = response.readEntity(new GenericType<List<Price>>(){});

Here is how to fix it

Change Moxy dependency to:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
</dependency>

and add annotations on 'Price' object.

@XmlRootElement
@JsonIgnoreProperties(ignoreUnknown = true)
J.Olufsen
  • 13,415
  • 44
  • 120
  • 185
  • Just by looking at this, if I put everything into a single JerseyTest to test, I can tell you that with what you've provided, it should work fine. Try doing it yourself, then try and narrow down the problem. I'm not sure what else to tell you. The only way I could see it _not_ working (in a real server env) is if you are using web.xml, and the ResourceConfig class isn't even being used. If that _is_ the case, see [here](http://stackoverflow.com/a/29275727/2587435) – Paul Samsotha Apr 27 '16 at 12:27
  • 1
    You're not even using the `ResourceConfig` in your post. You are creating a completely different one `createHttpServerWith(new ResourceConfig()..` – Paul Samsotha Apr 27 '16 at 12:36
  • ResourceConfig is defined in `Main` and `ApplicationConfig` but it looks like those two are not likned together.....maybe – J.Olufsen Apr 27 '16 at 12:37
  • Why would they be linked? Just use `createHttpServerWith(new ApplicationConfig())` – Paul Samsotha Apr 27 '16 at 12:37
  • Because `ApplicationConfig` defines `ApplicationBinder ` that tells app how to handle injections/bindings... Right? – J.Olufsen Apr 27 '16 at 12:40
  • Now it works when testing manyally but JUnit test fails for some strange reason. – J.Olufsen Apr 27 '16 at 12:46
  • It's not the same error is it? – Paul Samsotha Apr 27 '16 at 12:47
  • Nope, there is no errors on serverside, it just successfully (200 status code) returns empty prices json. (`[]`). While debugging it reaches `PricingResource` but not entering `findPrices()` method of `PricingService`. – J.Olufsen Apr 27 '16 at 12:49
  • 2
    The main thing you're missing the mock initialization of the data. You should do this inside your test method. Like you did in your previous question, which is now missing in this question – Paul Samsotha Apr 27 '16 at 12:58

4 Answers4

10

Forget the InjectableProvider. You don't need it. The problem is that the mock service is not the one being injected. It is the one created by the DI framework. So you are checking for changes on the mock service, which has never been touched.

So what you need to do is bind the mock with the DI framework. You can simply create another AbstractBinder for testing. It can be a simple anonymous one, where you will bind the mock

ResourceConfig config = new ResourceConfig(PricingResource.class);
config.register(new AbstractBinder() {
    @Override
    protected void configure() {
        bind(pricingServiceMock).to(PricingService.class);
    }
});

Here you are simply binding the mocked service. So the framework will inject the mock into the resource. Now when you modify it in the request, the changes will be seen in the assertion

Oh and you still need to do your when(..).then(..) to initialize the data in the mock service. That is also what you are missing

@Test
public void testFindPrices(){
    Mockito.when(pricingServiceMock.findSomething()).thenReturn(list);
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • I updated the test but it still fails: `assertTrue(prices.get(0).getId() == getMockedPrices().get(0).getId());` – J.Olufsen Apr 27 '16 at 13:12
  • Did you initialize the mock data with `when(..).thenXxx(..)`? – Paul Samsotha Apr 27 '16 at 13:14
  • I think so,my whole test is posted in `UPDATE3` – J.Olufsen Apr 27 '16 at 13:17
  • have you done any debugging? Like checking the list inside the resource method. Is the data in the list? If it is, then it seems to be a problem with serialization – Paul Samsotha Apr 27 '16 at 13:22
  • `getMockedPrices` returns valid list of prices. Then after `when(..)` executed `pricingServiceMock` object doesn't seem to have anything interesting. Same goes for `response`. – J.Olufsen Apr 27 '16 at 13:35
  • Im talking about in your _resource method_. Is the mock data there? If it is that means there is a problem serializing it from the server end – Paul Samsotha Apr 27 '16 at 13:37
  • I am confused. Debugger show no useful information. Maybe there is a line of code that can be included into @Test to verify that mocking actually working? – J.Olufsen Apr 27 '16 at 13:51
  • 1
    You are not getting what I am saying. The _resource method_ is the method with `@GET` on the server side. Check the list returned from calling the service from inside the resource method. You can add a simple S.O.P if you want. If the price is in the list after retriving it from the service, then that means that the server is having a problem serializing it the response – Paul Samsotha Apr 27 '16 at 13:57
1

I fixed this problem by adding the following dependency to my application. compile group: 'org.glassfish.jersey.containers.glassfish', name: 'jersey-gf-cdi', version: '2.14'

Then there is no need to have any "AbstractBinder" related code.

nrkkalyan
  • 179
  • 2
  • 5
  • First of all injected resources(beans) must be CDI-Aware. It must be detecteable by CDI. We can use bean-discovery-mode="all" - then CDI scan ALL classes or bean-discovery-mode="annotated" and MARK our class with PROPER annotation: from https://docs.jboss.org/cdi/spec/1.2/cdi-spec.html#default_bean_discovery. I prefer@Dependent or @RequestScoped – vigor Mar 06 '18 at 14:39
1

The same error arise if beans.xml is missing or placed in the wrong place. This helped me: Where should beans.xml be placed?

luca.vercelli
  • 898
  • 7
  • 24
0

Simply use the @Service annotation at the starting of the class you want to Inject.

import org.jvnet.hk2.annotations.Service

@Service

public class PricingService{....}
borchvm
  • 3,533
  • 16
  • 44
  • 45