Il y a des erreurs lors de l'utilisation de DI dans l'application Jersey Rest:
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)
Je suis assez nouveau dans le concept et il semble assez compliqué car il y a quelques exemples qui semblent être obsolètes. Si je comprends bien, il existe plusieurs façons de faire fonctionner l’ID: HK2 natif, Spring/HK2 Bridge. Qu'est-ce qui est plus facile et plus simple à configurer? Comment configurer par programme (pas un fan de XML) pour 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() ...
}
METTRE &AGRAVE; JOUR
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 sortie:
INFO: 1 * Client response received on thread main
1 < 200
1 < Content-Length: 4
1 < Content-Type: application/json
[{}]
Java.lang.AssertionError
Pendant le débogage:
prices.get(0)
est un objet Price
auquel null
est affecté à tous les champs.
UPDATE4:
Ajouté à configure()
:
config.register(JacksonFeature.class);
config.register(JacksonJsonProvider.class);
Maintenant, Junit produit un peu mieux:
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}]
En effet, la liste prices
a le nombre correct de prices
mais les champs de tous les prix sont null . Cela conduit à supposer que le problème pourrait être une entité de lecture:
List<Price> prices = response.readEntity(new GenericType<List<Price>>(){});
Remplacez la dépendance Moxy par:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
et ajouter des annotations sur l'objet 'Price'.
@XmlRootElement
@JsonIgnoreProperties(ignoreUnknown = true)
Oubliez la InjectableProvider
. Tu n'en as pas besoin. Le problème est que le service mock n'est pas celui qui est injecté. C'est celui créé par le framework DI. Donc, vous vérifiez les modifications sur le service factice, ce qui n’a jamais été touché.
Donc, ce que vous devez faire est de lier la maquette avec le cadre DI. Vous pouvez simplement créer une autre AbstractBinder
à tester. Ce peut être un simple anonyme, où vous lierez la maquette
ResourceConfig config = new ResourceConfig(PricingResource.class);
config.register(new AbstractBinder() {
@Override
protected void configure() {
bind(pricingServiceMock).to(PricingService.class);
}
});
Ici, vous liez simplement le service simulé. Le cadre va donc injecter la maquette dans la ressource. Maintenant, lorsque vous le modifiez dans la requête, les modifications apparaîtront dans l'assertion.
Oh, et vous devez toujours utiliser votre when(..).then(..)
pour initialiser les données dans le service fictif. C'est aussi ce qui vous manque
@Test
public void testFindPrices(){
Mockito.when(pricingServiceMock.findSomething()).thenReturn(list);
J'ai résolu ce problème en ajoutant la dépendance suivante à mon application . compiler le groupe: 'org.glassfish.jersey.containers.glassfish', nom: 'jersey-gf-cdi', version: '2.14'
Il n’est alors pas nécessaire d’avoir un code associé à "AbstractBinder".