How to inject multiple mocks of the same interface

2020-02-26 04:04发布

The Java class (called ServiceCaller) I wish to test has this:

@Autowired @Qualifier(value="serviceA")
SomeService serviceA;

@Autowired @Qualifier(value="serviceB")
SomeService serviceB;

(there's a doWork() method that will check a condition and call either A or B).

How do I inject a mock of each service into the appropriate variable?

My Junit has this:

@InjectMocks ServiceCaller classUnderTest = new ServiceCaller();

@Mock SomeService mockServiceA;
@Mock SomeService mockServiceB;

Yet when I run my tests to check that service A/B called under the correct condition, I get null pointers as the mock hasn't been injected.

Obviously its because of multiple dependencies on the same interface (SomeService). Is there a way to specify the qualifier when declaring the mock service? Or do I need to have setters for the dependencies and set the old fashioned way?

3条回答
Emotional °昔
2楼-- · 2020-02-26 04:41

When you have same type dependencies mockito stops injecting the depedencies due to properties of same types. To resolve this with reference to @osiris256 in the following way:

class ServiceLayer{

   @Autowired
   @Qualifier("bean1")
   private InterfaceA typeA;  

   @Autowired
   @Qualifier("bean2")
   private InterfaceA typeB;  

}

Your test class should be:

@RunWith(SpringRunner.class)    
class ServiceLayerTest{

  @Mock(name = "typeA")
  private InterfaceA typeA;  

  @Mock(name = "typeB")
  private InterfaceA typeB;  

  @InjectMocks
  ServiceLayer serviceLayer;

  @Before
  public void initialiseBeforeTest(){
     MockitoAnnotations.initMocks(this);
  }

  // here goes your test 
  @Test
   public void test(){
     // use your when then .....
  }
}

Note: if you are using SpringRunner and use @MockBean this will not work, you have to replace with @Mock(name="") with reference to @osiris256.

查看更多
狗以群分
3楼-- · 2020-02-26 04:43

It should be enough to name your mocks serviceA and serviceB. From Mockito documentation:

Property setter injection; mocks will first be resolved by type, then, if there is several property of the same type, by the match of the property name and the mock name.

In your example:

@InjectMocks ServiceCaller classUnderTest;

@Mock SomeService serviceA;
@Mock SomeService serviceB;

Note that it is not necessary to manually create class instance when using @InjectMocks.

Nevertheless I personally prefer injecting dependencies using constructor. It makes it easier to inject mocks in tests (just call a constructor with your mocks - without reflections tools or @InjectMocks (which is useful, but hides some aspects)). In addition using TDD it is clearly visible what dependencies are needed for the tested class and also IDE can generate your constructor stubs.

Spring Framework completely supports constructor injection:

@Bean
public class ServiceCaller {
    private final SomeService serviceA;
    private final SomeService serviceB;

    @Autowired
    public ServiceCaller(@Qualifier("serviceA") SomeService serviceA,
                         @Qualifier("serviceB") SomeService serviceB) { ... }

    ...
}

This code can be tested with:

@Mock SomeService serviceA;
@Mock SomeService serviceB;

//in a setup or test method
ServiceCaller classUnderTest = new ServiceCaller(serviceA, serviceB); 
查看更多
仙女界的扛把子
4楼-- · 2020-02-26 04:54

you can use "name" property to define your instance like this:

@Mock(name="serviceA") SomeService serviceA;
@Mock(name="serviceB") SomeService serviceB;
查看更多
登录 后发表回答