evgeniy44
evgeniy44

Reputation: 3162

How to access Spring Bean from JerseyTest subclass

Here is my abstract class which starts Jersey with given Spring context:

public abstract class AbstractJerseyTest extends JerseyTest {

public void setUp() throws Exception {
    super.setUp();
}

@AfterClass
public void destroy() throws Exception {
    tearDown();
}

@Override
protected URI getBaseUri() {
    return URI.create("http://localhost:9993");
}

@Override
protected Application configure() {
    RestApplication application = new RestApplication();

    Map<String, Object> properties = new HashMap<String, Object>();
    properties.put(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
    properties.put("contextConfigLocation", "classpath:spring-context-test.xml");

    application.setProperties(properties);
    application.register(this);
    return application;
}
}

So, the problem is that I need to access Spring bean from my test to populate database with some data.

Jersey version is 2.6

Also I found a similar question here

But it's related to Jersey 1.x so it doesn't work for Jersey 2.x

Could anyone point me in the right direction?

Upvotes: 1

Views: 596

Answers (2)

Paul Samsotha
Paul Samsotha

Reputation: 209004

Normally in your case, I'd just say work with mocks, but there are cases where you may need to expose the services in the test class.

To do this without any "ugly hacks", you will need to get a handle on the ServiceLocator (which is analogous to Spring's ApplicationContext). When the Jersey app boots up, all the Spring services from the ApplicationContext are put into the ServiceLocator through HK2's Spring bridge.

The problem is JerseyTest does not expose the ServiceLocator in any way. The only way I can think of to get a hold of it, is to create your own TestContainerFactory, and create the ApplicationHandler, which exposes the ServiceLocator.

Trying to implement your own TestContainerFactory is not a walk in the park, if you don't know what you're doing. The easiest thing to do is just look at the source code for Jersey's InMemoryTestContainerFactory. If you look at the constructor for the inner class InMemoryTestContainer, you will see it creating the ApplicationHandler. This is how you can expose the ServiceLocator, through the appHandler.getServiceLocator().

So if you copied that class, and exposed the ServiceLocator, you could create your JerseyTest extension, and call the ServiceLocator.inject(Object) method to inject the test class.

public abstract class AbstractServiceLocatorAwareJerseyTest extends JerseyTest {
    
    private final ServiceLocatorAwareInMemoryTestContainerFactory factory
            = new ServiceLocatorAwareInMemoryTestContainerFactory();
    private ServiceLocator locator;
    
    @Override
    public TestContainerFactory getTestContainerFactory() {
        return factory;
    }
    
    @Before
    @Override
    public void setUp() throws Exception {
        super.setUp();
        this.locator = factory.getServiceLocator();
        if (injectTestClass()) {
            this.locator.inject(this);
        }
    }
    
    public boolean injectTestClass() {
        return true;
    }
    
    public ServiceLocator getServiceLocator() {
        return locator;
    }
}

And if for any reason you needed it, the ServiceLocator also has the ApplicationContext, which you could also expose to your test class if needed.

I put together a GitHub project, with a complete implementation, with tests if you want to take a look at it.


UPDATE

Though the OP's answer to this question works, I believe the fact that it works, is a bug. I originally deleted this answer, after the OP posted their answer, but after some testing, I believe that solution is a bug, so I've undeleted this post for anyone who doesn't like the warning1 you get when you use that solution


1. "WARNING: A provider SimpleTest registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. Due to constraint configuration problems the provider SimpleTest will be ignored."

Upvotes: 1

evgeniy44
evgeniy44

Reputation: 3162

Solution was really simple. I added:

@Autowired
private Repository repository;

to the AbstractJerseyTest and this field was automatically autowired during test startup. I don't know details about how it works, but it seems that when I register instance of the test in REST application

application.register(this);

it automatically autowires all beans in the test.

Upvotes: 1

Related Questions