简体   繁体   中英

Java: Junit a class with Inject annotation

@Singleton
public class RealWorkWindow implements WorkWindow {

    @Inject private Factory myFactory;

    public RealWorkWindow (
            LongSupplier longSupplier
    ) {
        defaultWindow = myFactory.create(() -> 1000L);
        workWindow = myFactory.create(longSupplier);
    } 
    ...

As you can see I am Injecting a factory class (injected via FactoryModuleBuilder)

Test code

@Test
public class RealWorkWindowTest {
    private RealWorkWindow testWindow;

    @BeforeMethod
    void setup() {
        MockitoAnnotations.initMocks(this);

        testWindow = spy(new RealWorkWindow(() -> 1L));
    }

Factory.py

public interface RealWorkWindowFactory {
    RealWorkWindowFactory create(LongSupplier longSupplier);
}

Module

install(new FactoryModuleBuilder()
                        .implement(WorkWindow.class, RealWorkWindow.class)
                        .build(RealWorkWindowFactory.class));

When I run the test RealWorkWindowTest the test fails with NPE that factory does not exists, which makes sense since I dont think injection runs.

How can I test with Injection in junit? or mock properly?

Similar to the problem describe in https://mhaligowski.github.io/blog/2014/05/30/mockito-with-both-constructor-and-field-injection.html

But the problem that I have is that mock is used IN constructor so it's still a null when instantiate the test object (because i have not called Mockito.init yet)

If you use a MockitoJUnitRunner , you can use @Mock to create a mock for the Factory and inject it.

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock
    private Factory myFactory;

    @InjectMocks
    private RealWorkWindow realWorkWindow;

    @Test
    public void testSomething() {
        when(myFactory.create(/* insert param here */)).thenReturn(/* insert return value here */);

        /* perform your test */
    }
}

Use constructor injection when using @Assisted injection

Guice's Assisted Injection wiki page mentions:

AssistedInject generates an implementation of the factory class automatically. To use it, annotate the implementation class' constructor and the fields that aren't known by the injector:

And later:

AssistedInject maps the create() method's parameters to the corresponding @Assisted parameters in the implementation class' constructor. For the other constructor arguments, it asks the regular Injector to provide values.

As they are only available at that time, Guice will only inject fields after the constructor call. This translates for you in the fact that you must use the constructor injection , and no other mechanism (unless you have an extension that allows @PostConstruct or similar).

So let's rewrite your code according to that. Write your RealWorkWindow as follow:

@Singleton
public class RealWorkWindow implements WorkWindow {

  private final WorkWindow defaultWindow;
  private final WorkWindow workWindow;

  @Inject
  public RealWorkWindow(Factory myFactory, @Assisted LongSupplier longSupplier) {
    defaultWindow = myFactory.create(() -> 1000L);
    workWindow = myFactory.create(longSupplier);
  }

}

Your code can then become testable as follows:

@RunWith(MockitoJunitRunner.class)
public class RealWorkWindowTest {

  @Mock
  Factory myFactory;

  @Mock
  WorkWindow defaultWindow;

  @Mock
  WorkWindow workWindow;

  RealWorkWindow realWorkWindow;

  @BeforeEach
  void setup() {
    when(myFactory.create(any(LongSupplier.class)))
        .thenReturn(defaultWindow) // First call
        .thenReturn(workWindow);   // Second call
    realWorkWindow = new RealWorkWindow(myFactory, () -> 1000L);
  }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM