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>>(){});
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)
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);