How to mock Injected value relying on a qualifier using JerseyTest - java

I'm trying to mock a controller/resource including the jax-rs layer. The class has dependencies that need to be injected.
It however also has some String values that are injected using a qualifier interface.
Basically, I'm using JerseyTest to run a single controller and use HK2 for dependency injection. I created a ResourceConfig and registered a AbstractBinder to bind the injected classes.
This works fine for regular injected dependencies, but when the the additional #SomeQualifierInterface annotation is added, it crashes with the following error:
MultiException stack 1 of 3
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=String,parent=ThingsController,qualifiers={#com.company.SomeQualifierInterface()},position=-1,optional=false,self=false,unqualified=null,10035302)
...
MultiException stack 2 of 3
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.company.ThingsController errors were found
...
MultiException stack 3 of 3
java.lang.IllegalStateException: Unable to perform operation: resolve on com.company.ThingsController
...
See the simplified full code example below:
Controller / Resource
import org.slf4j.Logger;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
#Path("/things")
public class ThingsController {
#Inject
private Logger log;
#Inject
#SomeQualifierInterface
private String injectedQualifierValue;
#GET
public Response getThings() {
log.info("getting things");
System.out.println("Injected value: " + injectedQualifierValue);
return Response.status(200).entity("hello world!").build();
}
}
Qualifier interface
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
#Qualifier
#Retention(RUNTIME)
#Target({ TYPE, METHOD, FIELD, PARAMETER })
public #interface SomeQualifierInterface { }
Producing service
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
#ApplicationScoped
public class SomeProducerService {
#Produces
#Dependent
#SomeQualifierInterface
public String getQualifierValue() {
return "some value!";
}
}
Test
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.slf4j.Logger;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MockedThingsControllerTest extends JerseyTest {
private Logger logMock = mock(Logger.class);
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(ThingsController.class);
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure() {
bind(logMock).to(Logger.class);
bind("some mocked value").to(String.class); // Doesn't work
bind(new SomeProducerService()).to(SomeProducerService.class); // Doesn't work
}
});
return resourceConfig;
}
#Test
public void doSomething() {
Response response = target("/things").request().get();
assertEquals(200, response.getStatus());
verify(logMock).info("getting things");
}
}
POM
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.27.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>

Solved!
First, use the AbstractBinder from org.glassfish.hk2.utilities.binding.AbstractBinder instead of org.glassfish.jersey.internal.inject.AbstractBinder.
Second, create a class that extends AnnotationLiteral and implements the interface.
Last, bind the value to a TypeLiteral and set the qualifiedBy to the AnnotationLiteral.
Full code:
import org.glassfish.hk2.api.AnnotationLiteral;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.slf4j.Logger;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MockedThingsControllerTest extends JerseyTest {
private Logger logMock = mock(Logger.class);
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(ThingsController.class);
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure() {
bind(logMock).to(Logger.class);
bind("some mocked value").to(new TypeLiteral<String>() {}).qualifiedBy(new SomeQualifierLiteral());
}
});
return resourceConfig;
}
#Test
public void doSomething() {
Response response = target("/things").request().get();
assertEquals(200, response.getStatus());
verify(logMock).info("getting things");
}
static class SomeQualifierLiteral extends AnnotationLiteral<SomeQualifierInterface> implements SomeQualifierInterface {}
}

Related

Jersey 3 - Configuring binding with bindFactory

Using Jersey 3.0.1, I am struggling to get binding working.
I have this binding module with the factories below:
public static class MyBinder extends AbstractBinder {
#Override
protected void configure() {
LOG.info("Attempting to configure binder");
bindFactory(DataSourceFactory.class).to(HikariDataSource.class).in(Singleton.class);
bindFactory(JooqConfigFactory.class).to(Configuration.class).in(Singleton.class);
bindFactory(DSLContextFactory.class).to(DSLContext.class).in(Singleton.class);
LOG.info("Configured binder");
}
}
public static class DataSourceFactory implements Supplier<HikariDataSource> {
#Override
public HikariDataSource get() {
...
return new HikariDataSource(config);
}
}
public static class JooqConfigFactory implements Supplier<Configuration> {
#Inject
HikariDataSource dataSource;
#Override
public Configuration get() {
...
return conf;
}
}
public static class DSLContextFactory implements Supplier<DSLContext> {
#Inject
Configuration config;
#Override
public DSLContext get() {
return DSL.using(config);
}
}
Then I have the setup for my Servlet using embedded Jetty:
public void start() throws Exception {
int port = appConfig.getProperty("http.port", 9998);
Server server = new Server(port);
ServletContextHandler ctx =
new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
ctx.setContextPath("/");
server.setHandler(ctx);
ResourceConfig config = new JerseyConfig();
ServletHolder servlet = new ServletHolder(new ServletContainer(config));
servlet.setInitOrder(1);
ctx.addServlet(servlet, "/*");
server.start();
server.join();
}
public static class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("com.sodonnell.jersey", "jersey.config.server.provider.packages");
register(new MyBinder());
}
}
And in my Rest service I simply try to inject a private instance variable:
public MyClass {
#Inject // javax.inject.Inject
private DSLContext dslContext;
}
However this dslContext is always null. I can see from the logs, that it prints the LOG.info("Configured binder"); message. However putting similar logs in my factory classes show they never get called.
Has anyone got any idea what I am missing?
EDIT
To make things simpler, I created this class:
public class SimpleClass {
private static Logger LOG = LoggerFactory.getLogger(SimpleClass.class);
public SimpleClass() {
LOG.info("Call the simple class constructor");
}
Changed my binder module:
import com.google.inject.Injector;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.Properties;
import java.util.function.Supplier;
...
// This is a nested class
public static class MyBinder extends AbstractBinder {
#Override
protected void configure() {
LOG.info("Attempting to configure binder");
bind(new SimpleClass()).to(SimpleClass.class);
}
}
Then attempted to inject just SimpleClass:
package com.sodonnell.hdfs3.rest;
import com.sodonnell.hdfs3.SimpleClass;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.HEAD;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import org.jooq.DSLContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
#Inject
private SimpleClass simpleClass;
...
But its still null, although I see both the log messages. There must be some fundamental setup I am missing.
Full cut down code with the SimpleClass example at:
github.com/sodonnel/jerseyBind
The answer is quite simple. You are using Jersey 3.0 which has switched to the new Jakarta naming. javax is thrown out the window - this includes javax.inject. All the javax package names have now been changed to jakarta. So to get the inject to work, the #Inject import should be
import jakarta.inject.Inject;
This change is part of the change of Java EE to Jakarta EE Starting from Jakarta EE 8 to Jakarta EE 9, all the namespacing has changed from javax to jakarta. So things like javax.servlet will now be jakarta.servlet. Weird, yes a huge breaking change with no backward compatibility.
In your case you have all the correct components to work with Jakarta (i.e. Jersey 3.0 and Jett 11), but you just need to make use of the new namespacing. Notice all the JAX-RS imports are now jakarta also.

Inject method parameter with custom annotation with Jersey 2.29

I am upgrading a legacy project from Jersey 2.22 to the latest version, and I have some problem migrating our dependency injection.
We were using the custom annotation to inject the user / token in the method parameter :
#GET
public Response getAll(#SdnBasicAuth final UserManagement.User user) {
// ... Check user rights return Response.accepted().build();
}
And now I cannot get a working solution with the new Jersey decoupled dependency injection.
The system behavior is the following :
At the startup of the application the getAll method is called twice with my dummy user (that's not the expected behavior, we want only this call in response to a request).
Both supplier and resolver are called at the startup but not when I make a "real" request.
Note : In the Resolver the parameter final ServiceHandle<?> root is null.
How can I reproduce the behavior of the Jersey 2.22 (see code at the bottom of the post)
Thanks for helping.
The new code version with jersey 2.29 :
Dependencies list :
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-core</artifactId>
<version>${hk2.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-api</artifactId>
<version>${hk2.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-locator</artifactId>
<version>${hk2.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
The Binder :
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Singleton;
import javax.ws.rs.core.GenericType;
public class BasicAuthBinder extends AbstractBinder {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthBinder.class);
#Override
protected void configure() {
bindFactory(BasicAuthSupplier.class)
.to(UserManagement.User.class)
.proxyForSameScope(false)
.in(Singleton.class);
// I have try to change to this :
// .in(RequestScoped.class);
// But after that I have the following exception at startup.
// org.glassfish.hk2.api.MultiException: A MultiException has 1 exceptions.
// They are:
// java.lang.IllegalStateException: Not inside a request scope.
bind(BasicAuthResolver.class)
.to(new GenericType<InjectionResolver<SdnBasicAuth>>() {})
.in(Singleton.class);
}
}
The resolver
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import javax.inject.Inject;
import javax.inject.Named;
public class BasicAuthResolver implements org.glassfish.hk2.api.InjectionResolver<SdnBasicAuth> {
#Inject
#Named (InjectionResolver.SYSTEM_RESOLVER_NAME)
org.glassfish.hk2.api.InjectionResolver<Inject> systemInjectionResolver;
#Override
public Object resolve(final Injectee injectee, final ServiceHandle<?> root) {
if (UserManagement.User.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, root);
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return true;
}
}
The supplier:
import java.util.function.Supplier;
public class BasicAuthSupplier implements Supplier<UserManagement.User> {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthSupplier.class);
public BasicAuthSupplier() {
}
#Override
public UserManagement.User get() {
// Dummy code to create the user.
return UserManagement.User.newUser().build();
}
}
And the API :
#Path ("/my-path")
#Consumes (APPLICATION_JSON)
#Produces (APPLICATION_JSON)
public class {
#GET
public Response getAll(#SdnBasicAuth final UserManagement.User user) {
// ... Check user rights
return Response.accepted().build();
}
}
Below was the working code on jersey 2.22
Annotation:
#Target ({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention (RetentionPolicy.RUNTIME)
#Documented
public #interface SdnBasicAuth {
}
Binder :
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Singleton;
public class BasicAuthBinder extends AbstractBinder {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthBinder.class);
private final UserManagement userManagement;
public BasicAuthBinder(final UserManagement userManagement) {
this.userManagement = userManagement;
}
#Override
protected void configure() {
bind(userManagement).to(UserManagement.class);
bind(BasicAuthFactory.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(BasicAuthResolver.class)
.to(new TypeLiteral<InjectionResolver<SdnBasicAuth>>() {
})
.in(Singleton.class);
}
}
Factory :
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.model.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
#Singleton
public class BasicAuthFactory extends AbstractValueFactoryProvider {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthFactory.class);
#Inject
private UserManagement userManagement;
#Inject
public BasicAuthFactory(MultivaluedParameterExtractorProvider mpep, ServiceLocator injector) {
super(mpep, injector, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (parameter.getAnnotation(SdnBasicAuth.class) == null) {
LOG.debug("Not injecting user management provider.");
return null;
}
if (classType == null || (!classType.equals(UserManagement.User.class))) {
LOG.warn("IdentityParam annotation was not placed on correct object type; Injection might not work correctly!");
return null;
}
return new IdentityParamValueFactory(userManagement);
}
private static final class IdentityParamValueFactory extends AbstractContainerRequestValueFactory<UserManagement.User> {
private final UserManagement userManagement;
#Context
private ResourceContext context;
public IdentityParamValueFactory(final UserManagement userManagement) {
this.userManagement = userManagement;
}
public UserManagement.User provide() {
final HttpServletRequest request = context.getResource(HttpServletRequest.class);
// Dumb code to do the authorization stuff not related of our problem.
return UserManagement.User.newUser().build();
}
}
}
The resolver:
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
import javax.inject.Singleton;
#Singleton
public class BasicAuthResolver extends ParamInjectionResolver<SdnBasicAuth> {
public BasicAuthResolver() {
super(BasicAuthFactory.class);
}
}

ArgumentCaptor and comparing with hasItems

i am already desperate, i cannot find out why this test is not evaluated as successful. I have checked it milion times:
package someptest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import java.sql.SQLException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import somep.Order;
import somepBO.BOException;
import somepdao.OrderDAO;
public class XXX {
#Mock
OrderDAO dao;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void captor_A() throws SQLException, BOException {
Order order = new Order();
ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
when(dao.read(any(Integer.class))).thenReturn(order);
dao.read(123);
dao.read(456);
verify(dao, times(2)).read(intCaptor.capture());
#SuppressWarnings("unused")
List<Integer> xs = intCaptor.getAllValues();
assertThat(intCaptor.getAllValues(), hasItems(456));
}
}
Here is a screen from my debugging, captor catches correct values, but assertThat does not accept it, why?
Phew, something seems to go quite wrong on your machine. Based on your test, I created the following self-contained test class:
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
public class ArgumentCaptorTest {
private static class Order {
}
public static class OrderDAO {
public Order read(Integer any) {
return null;
}
}
#Mock
OrderDAO dao;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void captor_A() {
Order order = new Order();
ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
when(dao.read(any(Integer.class))).thenReturn(order);
dao.read(123);
dao.read(456);
verify(dao, times(2)).read(intCaptor.capture());
assertThat(intCaptor.getAllValues(), hasItems(456));
}
}
Used dependencies (pom.xml):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>testing</groupId>
<artifactId>testing</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.18.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
When I start the test, it runs perfectly and gives me a green bar. For what I see, you're doing it right. Maybe you have version conflicts between JUnit, Hamcrest and Mockito that lead to your error? I used these JAR versions:
junit:junit:4.12
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:2.18.3
I also left the imports so you can compare them with yours (just in case that a "wrong" import causes the error). As you statically import Matchers.* from the Hamcrest package, I am quite sure that this and/or your used library versions cause your problem.

How to test RESTful application?

I have an application example with a service:
RestApp.java
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/webapi")
public class RestApp extends Application {
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(MessageService.class);
return classes;
}
}
MessageService.java
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.List;
#Stateless
#Path("/messages")
public class MessageService {
#Inject
private MessagesManager messagesManager;
#GET
#Path("all")
#Produces({MediaType.APPLICATION_JSON})
public List<Message> getMessages() {
return messagesManager.getMessages();
}
}
and the service depends on the singleton MessagesManager.java:
import javax.ejb.*;
import javax.inject.Singleton;
#Singleton
#Startup
#ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class MessagesManager implements Serializable {
private List<Message> messages = new ArrayList<>();
#Lock(LockType.READ)
public List<Message> getMessages() {
messages.add(new Message(1, "message text"));
return messages;
}
}
and this app works fine. But during the test occurs error of injection:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=MessagesManager,parent=MessageService,qualifiers={},position=-1,optional=false,self=false,unqualified=null,1232089028)
Test code is:
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static org.junit.Assert.assertEquals;
public class RestAppTest extends JerseyTest {
#Override
protected Application configure() {
return new ResourceConfig(MessageService.class);
}
#Test
public void testGet() {
final Response response = target("messages/all").request().get();
assertEquals(200, response.getStatus());
}
}
Why it happens and how to fix it?
The class MessagesManager is missing in an application context. Add the class to configure method like this:
return new ResourceConfig(MessageService.class, MessagesManager.class);
You need couple of things
1> Well formed JSON structure for your REST API
2> Some kind of REST client such as advanced REST client for chrome, Mozilla etc which can be used as a plugin. POSTMAN is also a useful tool

Spring with JUnit Testing and Dependency Injection does not work

I try to use Springs own Dependency Injection in a Junit test case:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
//#SpringApplicationConfiguration(classes = {AppConfig.class})
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner; //SUT
#Before
public void setUp() {
// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SatzRechnerTest.class);
//satzRechner=context.getBean(SatzRechner.class);
}
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
Im testing a class names SatzRechner in which Spring should also autowire some variables. Here is my Class under test:
#Component
public class SatzRechner {
#Autowired //#Inject
private Rechner taschenRechner;
#Autowired
private Zahlenfabrik zahlenfabrik;
public Integer summe(String zeichenSatz) {
return taschenRechner.summe(zahlenfabrik.erzeugeZahlen(zeichenSatz));
}
}
And AppConfig.class which is using as Configurationfile looks like that:
#Configuration
#ComponentScan(value={"org.binarisinformatik"})
public class AppConfig {
}
What is here the problem?
If you want to use a Spring configuration class, this one must have beans definitions. Please find an example below :
Test class:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner;
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
}
Configuration class :
You have to declare #Bean annotated methods. These beans are managed by Spring container.
#Configuration
public class AppConfig {
// Beans present here will be injected into the SatzRechnerTest class.
#Bean
public SatzRechner satzRechner() {
return new SatzRechner();
}
#Bean
public Rechner taschenRechner() {
return new TaschenRechner();
}
#Bean
public Zahlenfabrik zahlenfabrik() {
return new Zahlenfabrik();
}
}
Note : I let you properly handle returned types here and beans parameters (if present in your context).
There are two things you have to ensure before you run the test case successfully:
1) Classes SatzRechner, Rechner & Zahlenfabrik should be under "org.binarisinformatik" package
2) Classes Rechner & Zahlenfabrik should also be annotated with #Component as SatzRechner.

Categories