Tuesday, August 2, 2016

Using Guice for Play tests

The earlier post Unit testing and mocking with PowerMock in Play explains, how to define unit tests with the class WithApplication and Helpers.fakeApplication(). Such test is executed with the default injection binding, which is defined in a Play module. The default binding may be not appropriate for a particular test.

The Play class GuiceApplicationBuilder allows to override the default module binding for specific tests.
Note, that binding overriding with GuiceApplicationBuilder is possible only if dependency injection is done with Guice - the Play default (see Testing with Guice of Play manual).

Overriding dependency injection binding for tests


The post Custom injection binding with Guice in Play shows an example of flexible injection binding with a module method configure.
Injection binding in the example is done like this (the original example code is a bit different):
 public class Module extends AbstractModule { 
      private final Environment environment;
      public Module(Environment environment, Configuration configuration) {
        this.environment = environment;
       }
      @Override  
      protected void configure() {
         bind(SessionProvider.class).to(SessionInCashProvider.class);  
         if (environment.isTest() ) {
          bind(CacheProvider.class).to(FakeCacheProvider.class);
        }
        else {
          bind(CacheProvider.class).to(RuntimeCacheProvider.class);   
        }
      }  
 }  
The class SessionInCacheProvider implements an interface SessionProvider and has an injected data member cacheProvider:
 public class SessionInCacheProvider implements SessionProvider {  
      @Inject   
      private CacheProvider cacheProvider;  
     ...  
 }  
The class RuntimeCacheProvider implements the interface CacheProvider for a real application, while FakeCacheProvider is a dummy implementation to be used for tests. The decision, which implementation should be used, is done by the module itself according to the environment.
Such method of binding has a serious disadvantage, since it couples between application and tests. Besides, it replaces the binding for all tests globally.

Overriding of injection binding for tests with GuiceApplicationBuilder is the better alternative. The example below shows how this may be done.

1. A module implementation of dependency injection binding is not aware, which binding should be done for tests:
 public class Module extends AbstractModule { 
      private final Environment environment;
      public Module(Environment environment, Configuration configuration) {
        this.environment = environment;
      }
      @Override  
      protected void configure() {
         bind(SessionProvider.class).to(SessionInCashProvider.class);   
         if (!environment.isTest() ) {
          bind(CacheProvider.class).to(RuntimeCacheProvider.class);   
        }
      }  
 }  
2. Class FakeApplicationTestBase overrides the default dependency injection binding for the interface CacheProvider:
 package utils;  
 import static play.inject.Bindings.bind;  
 import guiceExamples.cache.CacheProvider;  
 import play.Application;  
 import play.Mode;  
 import play.inject.guice.GuiceApplicationBuilder;  
 import play.test.WithApplication;  
 public abstract class FakeApplicationTestBase extends WithApplication {  
      @Override  
      protected Application provideApplication() {  
           return new GuiceApplicationBuilder()  
                .overrides(bind(CacheProvider.class).to(FakeCacheProvider.class))  
                .in(Mode.TEST)  
                .build();  
      }
      protected Injector getInjector() {
           return app.injector();
      }
 }  
3. Test class derives from FakeApplicationTestBase. It injects an instance of a sessionProvider with fake application injector:
 public class TestInCacheSessionProvider extends FakeApplicationTestBase {  
      static private String FAKE_USER ="FAKE_USER";  
      static private String FAKE_PASSWORD = "FAKE_PASSWORD";  
      static private String FAKE_DATA = "FAKE_DATA";  
      private SessionProvider sessionProvider;  
      @Before  
      public void setUp() {  
           sessionProvider = getInjector().instanceOf(SessionProvider.class);  
      }  
      @Test  
      public void testEncodeDecodeSession() {  
           SessionDTO sessionId = sessionProvider.startSession(FAKE_USER, FAKE_PASSWORD);  
           assertNotNull(sessionId);  
           sessionId.setData(FAKE_DATA);  
           String sessionEncrypt = sessionProvider.encryptSession(sessionId);  
           assertFalse(sessionEncrypt.isEmpty());  
           SessionDTO restoredSessionId = sessionProvider.restoreSession(sessionEncrypt);  
           assertNotNull(restoredSessionId);  
           assertEquals(sessionId.getUuid(), restoredSessionId.getUuid());  
           assertEquals(sessionId.getData(), restoredSessionId.getData());  
      }  
 }  
Injection with GuiceApplicationBuilder detaches between application and tests. It even allows to define different bindings for different tests - just with another class that extends the WithApplication.

Running test with a fakeApplication with different injection binding


The above test testEncodeDecodeSession creates an instance of a sessionProvider by explicit call to the fake application injector.
If a test should be run with a fake application, the proper method for fakeApplication creation should be used. Call to Helpers.running method should be done with provideApplication() instead of Helpers.fakeApplication():
 public class TestInCacheSessionProvider extends FakeApplicationTestBase { 
      import static play.test.Helpers.running;
      ...
      @Test  
      public void testSomething() {  
          running(provideApplication(), () -> {  
               // put test stuff  
               // put asserts  
           });
     }
}  
Call to Helpers.fakeApplication() will create an application with the default injection binding.

See on Git working code examples.

No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top