Quantcast

Sunday, October 26, 2008

Integrating Google Guice into JUnit4 tests

You can integrate Guice into your JUnit 4 tests with three simple steps:

  1. Add the following class to your classpath:
    package com.google.inject.junit;
    
    import com.google.inject.Guice;
    import com.google.inject.Injector;
    import com.google.inject.Module;
    import java.util.List;
    import org.junit.runners.BlockJUnit4ClassRunner;
    import org.junit.runners.model.InitializationError;
    
    /**
     * Uses Guice to inject JUnit4 tests.
     *
     * @author Gili Tzabari
     */
    public class GuiceTestRunner extends BlockJUnit4ClassRunner
    {
      private final Injector injector;
    
      /**
       * Creates a new GuiceTestRunner.
       *
       * @param classToRun the test class to run
       * @param modules the Guice modules
       * @throws InitializationError if the test class is malformed
       */
      public GuiceTestRunner(final Class<?> classToRun, Module... modules) throws InitializationError
      {
        super(classToRun);
        this.injector = Guice.createInjector(modules);
      }
    
      @Override
      public Object createTest()
      {
        return injector.getInstance(getTestClass().getJavaClass());
      }
    
      @Override
      protected void validateZeroArgConstructor(List<Throwable> errors)
      {
        // Guice can inject constructors with parameters so we don't want this method to trigger an error
      }
    
      /**
       * Returns the Guice injector.
       *
       * @return the Guice injector
       */
      protected Injector getInjector()
      {
        return injector;
      }
    }

  2. Customize GuiceTestRunner for your specific project by subclassing it. For example:
    package myproject;
    
    import com.google.inject.junit.GuiceTestRunner;
    import com.wideplay.warp.persist.PersistenceService;
    import myproject.GuiceModule;
    import org.junit.runners.model.InitializationError;
    
    /**
     * JUnit4 runner customized for our Guice module.
     *
     * @author Gili Tzabari
     */
    public class GuiceIntegration extends GuiceTestRunner
    {
      /**
       * Creates a new GuiceIntegration.
       *
       * @param classToRun the test class to run
       * @throws InitializationError if the test class is malformed
       */
      public GuiceIntegration(Class classToRun) throws InitializationError
      {
        super(classToRun, new GuiceModule());
      }
    }

  3. Add @RunWith to all your JUnit test classes. For example:

    @RunWith(GuiceIntegration.class)
    public class MyTest
    {
      @Inject
      public MyTest(SomeDependency foo)
      {
        ...
      }
    }

That's it! Guice will now inject your test classes.

EDIT: I'm a convert :) I now use AtUnit to integrate Guice into JUnit.

6 comments:

Guðmundur Bjarni said...

Hi Gili,

I found this post through the guice mailing list. I wrote a similar solution for having Guice aware unit tests. My solution used an annotation for specifying which modules to use:

@GuiceModules({ Module1.class, Module2.class })
@RunWith(GuiceTestRunner.class)
public class ...

The obvious downside is the lack of giving constructor arguments to the modules and the use of reflection behind the scenes.

I kind of like your solution is cleaner than mine, so I'm thinking of using your approach. :)

regards,
Guðmundur Bjarni

Logan Johnson said...

You might want to check out guiceberry and AtUnit for this, as well.

Gili said...

It's funny, it didn't actually occur to me to use annotations for this :)

Enjoy!

Gili said...

Logan,

TestScope definitely looks nice, but the rest of it seems too verbose for my taste. Maybe I'm wrong and I'll end up needing this in the end but I'd like to try without it first.

I'm looking into using JUnit4, Guice and Mockito: http://code.google.com/p/mockito/

It looks like this approach might involve less code and hopefully also less new concerns to learn.

Logan Johnson said...

Gili,

I should have been clearer... guiceberry and atunit are separate frameworks, not intended to be used together. If guiceberry is too verbose, do check out atunit.

Most of my AtUnit tests extend a base class that has only the class-level @RunWith, @MockFramework, and @Container annotations. The test classes themselves have no added code except for the @Inject, @Mock, and @Stub annotations on their fields.

That said, I also really like the look of Mockito for mock objects I'm looking forward to seeing what you settle on.

Cristiano Gavião said...

Hi,

Could you tell me if there is a way to use the instance of test class created by injector.getInstance(getTestClass().getJavaClass()) to fill some created bind or maybe to create a bind at runtime, so than I could to inject it in another class?

thanks