Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guice, DI, and Unit Tests in Play 2.4

So I've been trying to figure this out on my own via documentation but I'm not getting anywhere.

I've got some simple DI bindings setup in a service class that creates a repository object. Simple. However, when I run this in test mode, @Inject does nothing and the repository object is never instantiated.

@Inject
TagRepository tagRepository;

So on the line where it's use, in test mode, we of course get a NullPointerException

tagRepository.tagExistsByName(tag);

This bubbles up into my test like so:

[error] Test services.TagsServiceTest.testAddNewTag failed: java.lang.NullPointerException: null, took 0.097 sec
[error]     at services.TagService.tagExists(TagService.java:27)
[error]     at services.TagService.addNewTag(TagService.java:18)
[error]     at services.TagsServiceTest.testAddNewTag(TagsServiceTest.java:29)

My question is, how do I configure my application to use Guice injectors in test mode? I didn't have this problem with my controllers because requests were actually being made to them, setting up the full application.

One thing I should mention is that I'm using a provider to provide my app to the tests. Should I be using the Guice application builder? If so, where does that go? The play docs aren't very helpful in that regard. Here is the provider

@Override
protected FakeApplication provideFakeApplication() {
    return new FakeApplication(new java.io.File("."), Helpers.class.getClassLoader(), ImmutableMap.of("play.http.router", "router.Routes"), new ArrayList<String>(), null);
}

UPDATE:

Here is the update based on the suggestion below

Inside my BaseTest class

    @Override
    protected Application provideApplication() {
        return new GuiceApplicationBuilder().in(Mode.TEST).build();
    }

And then in the service testing class

    @Before
    public void beforeTest() {
        Injector injector = new GuiceInjectorBuilder().bindings(bind(TagService.class).toInstance(new TagService())).injector();
        tagService = injector.instanceOf(TagService.class);
    }

However, I'm still getting null pointer exceptions because the TagRepository isn't being injected.

ANSWER:

I was thinking about this slightly wrong. If you setup the injector with the object you need to inject, then create an instance from that, you won't get any more NullPointerExceptions

@Before
public void beforeTest() {
    Injector injector = new GuiceInjectorBuilder().bindings(bind(TagRepository.class).toInstance(new TagRepository())).injector();
    tagService = injector.instanceOf(TagService.class);
}
like image 957
Zarathuztra Avatar asked Aug 29 '15 19:08

Zarathuztra


1 Answers

If you're extending WithApplication, you can override protected Application provideApplication() to return an Application built using GuiceApplicationBuilder.

For example (based on this code) you can create your app, add or override bindings, etc, and set the mode. If you make the class abstract, it will then work automatically with all child classes.

public abstract class AbstractFakeApplicationTest extends WithApplication
{
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFakeApplicationTest.class);

    @Override
    protected Application provideApplication()
    {
        return new GuiceApplicationBuilder().in(Mode.TEST)
                                            .build();
    }

    @Override
    public void startPlay()
    {
        super.startPlay();
        // mock or otherwise provide a context
        Http.Context.current.set(new Http.Context(1L,
                                                  Mockito.mock(RequestHeader.class),
                                                  Mockito.mock(Http.Request.class),
                                                  Collections.<String, String>emptyMap(),
                                                  Collections.<String, String>emptyMap(),
                                                  Collections.<String, Object>emptyMap()));
    }

    public Http.Context context()
    {
        return Http.Context.current.get();
    }
}

Child classes then just extend this class and test as normal - all DI should occur as it does when you run the app normally.

You can see various examples of it here

This gives the basic outline for what you need to do. Hopefully the docs at https://playframework.com/documentation/2.4.x/JavaTestingWithGuice will make a bit more sense now.

like image 68
Steve Chaloner Avatar answered Oct 17 '22 06:10

Steve Chaloner