javaunit-testingdependency-injectionjerseyjersey-2.0

Jersey 2 inject dependencies into unit test


I have a controller like this

@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AccountController implements CRUDController<Long, Account> {

    private AccountDao accountDao;
    private AccountService accountService;

    @Inject
    public AccountController(AccountDao accountDao, AccountService accountService) {
        this.accountDao = accountDao;
        this.accountService = accountService;
    }
...

I'm injecting AccountDao and AccountService using

ResourceConfig config = new ResourceConfig()
               .packages("controller", "exception")
               .register(new MyDIBinder());

Where MyDIBinder is contains all the bindings (e.g

AccountDaoImpl accountDaoImpl = new AccountDaoImpl();
bind(accountDaoImpl).to(AccountDao.class);

)

Now I want to write a unit test for this controller, is it possible to inject the whole AccountController instance with all of it's transitive dependencies into the test?

Something like

    @Inject
    AccountController accountController;

Solution

  • You can use the main IoC container, and just explicitly inject the test class. Jersey uses HK2 as its DI framework, and its IoC container is the ServiceLocator, which has a method inject(anyObject) that can inject any objects with dependencies that are in its registry.

    For example you could do something like

    public class InjectionTest {
    
        @Inject
        private TestController controller;
    
        @Before
        public void setUp() {
            final Binder b = new AbstractBinder() {
                @Override
                public void configure() {
                    bindAsContract(TestController.class);
                }
            };
            final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestBinder(), b);
            locator.inject(this);
        }
    
        @Test
        public void doTest() {
            assertNotNull(controller);
            String response = controller.get();
            assertEquals("Hello Tests", response);
        }
    }
    

    The ServiceLocatorUtilities class is a helper class that allows us to easily create the ServiceLocator, and then we just call inject(this) to inject the InjectionTest.

    If it seems repetitive to do this for all your controller tests, you may want to create an abstract base test class. Maybe something like

    public abstract class AbstractControllerTest {
    
        protected ServiceLocator locator;
        private final Class<?> controllerClass;
    
        protected AbstractControllerTest(Class<?> controllerClass) {
            this.controllerClass = controllerClass;
        }
    
        @Before
        public void setUp() {
            final AbstractBinder binder = new AbstractBinder() {
                @Override
                public void configure() {
                    bindAsContract(controllerClass);
                }
            };
            locator = ServiceLocatorUtilities.bind(new TestBinder(), binder);
            locator.inject(this);
        }
    
        @After
        public void tearDown() {
            if (locator != null) {
                locator.shutdown();
            }
        }
    }
    

    Then in your concrete class

    public class TestControllerTest extends AbstractControllerTest {
    
        public TestControllerTest() {
            super(TestController.class);
        }
    
        @Inject
        private TestController controller;
    
        @Test
        public void doTest() {
            assertNotNull(controller);
            assertEquals("Hello Tests", controller.get());
        }
    }
    

    If you spent some more time, I'm sure you could come up with a better abstract test class design. It was the first thing that came to mind for me.

    Note: For anything request scoped, you mayb need to just mock it. When running the unit tests, there is no request context, so the test will fail.

    See Also: