javadependency-injectionjerseyglassfishhk2

UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl


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)

Solution

  • 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);