4

I have a class Dummy. I inject 3 variables. However, one of them is not injectable because it is an interface. So I inject an object, one of whose methods return the needed type.

Class Dummy
{
   private final Class1 class1;
   private final Class2 class2
   private final Interface1 interface1;

  @Inject
  Dummy(Class1 class1, Class2 class2, HelperClass helperclass) 
  {
       this.class1 = class1;
       this.class2 = class2;
       this.interface1 = helperclass.somefunction();
  }
}

The HelperClass's somefunction returns an instance of Interface1.

This is my test:

@RunWith(MockitoJUnitRunner.class)
Class DummyTest 
{
  @Mock 
  private Class1 class1;

  @Mock
  private Class2 class2;

  @Mock
  private HelperClass helperclass;

  @InjectMocks
  private Dummy dummy;

  @Before
  public void start() 
  {
    Interface1 mockInterface = mock(Interface1.class);
    when(helperclass.somefunction()).thenReturn(mockInterface);
  }
  @Test
  public void test()
  {
   // etc...
  }
}

However, interface1 is null when I run the test. What am I doing wrong?

durron597
  • 31,968
  • 17
  • 99
  • 158
Thiyagu
  • 17,362
  • 5
  • 42
  • 79

1 Answers1

5

@InjectMocks happens before the @Before annotation.

For this reason (and other reasons), I recommend not using @InjectMocks at all; just build your SUT class in the @Before method with a real constructor.

This ordering is obvious when you add a few print statements to your test class. I removed all the Class1 and Class2 stuff as it's not relevant. See this code run:

@RunWith(MockitoJUnitRunner.class)
public class DummyTest {
  @Mock
  private HelperClass helperclass;

  @InjectMocks
  private Dummy dummy;

  @Before
  public void start() 
  {
    System.out.println("In @Before!");
    Interface1 mockInterface = mock(Interface1.class);
    when(helperclass.somefunction()).thenReturn(mockInterface);
  }
  
  @Test
  public void test()
  {
    System.out.println("In @Test!");
  }
  
  public static class Dummy {
    public final Interface1 interface1;
    public final HelperClass helperclass;

   @Inject
   Dummy(HelperClass helperclass) 
   {
     System.out.println("In dummy constructor!");
        this.interface1 = helperclass.somefunction();
        this.helperclass = helperclass;
   }
 }
  
  private static class HelperClass {
    Interface1 somefunction() {
      return new Interface1() {};
    }
  }
  
  private static interface Interface1 {
    
  }
}

Output:

In dummy constructor!
In @Before!
In @Test!

If you insist on doing it with @Mock and @InjectMocks, you could try using the answer argument instead:

@Mock(answer=Answers.RETURNS_MOCKS)

will make helperclass.somemethod() return a mock instead of null.


Honestly, it's not surprising that it works this way. The authors of Mockito really don't like Partial Mocks / Stubs, and explicitly say so in their documentation:

As usual you are going to read the partial mock warning: Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects. How does partial mock fit into this paradigm? Well, it just doesn't... Partial mock usually means that the complexity has been moved to a different method on the same object. In most cases, this is not the way you want to design your application.

However, there are rare cases when partial mocks come handy: dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.) However, I wouldn't use partial mocks for new, test-driven & well-designed code.

Having helperclass return something other than null is a partial mock, and therefore they won't like it.

Community
  • 1
  • 1
durron597
  • 31,968
  • 17
  • 99
  • 158