Cyriac George
Cyriac George

Reputation: 153

Mock an autowired spring dependency (Interface) which is not directly used by the test class

Question (in short) - how do I mock a spring dependency that is not directly referenced in my JUnit.

Below is the setup that am working with. ServiceA is an interface and its implementation requires a handful of autowired dependencies for its many logic to work.

public interface ServiceA{
    void invoke();
    ...10 more methods
}

@Service
public class ServiceAImpl implements ServiceA{
    @Autowired
    private ServiceB serviceB; 

    @Autowired
    private ServiceC serviceC;

    @Autowired
    private DAOA daoA;

    @Autowired
    private DAOB daoB;

    @Override
    public void invoke(arg1, arg2){
        // use above dependencies to do something
        serviceB.sendEmail(args);
    }
}

ServiceB sends an email by invoking yet another util dependency.

public interface ServiceB{
    void sendEmail();
    ...10 more methods
}

@Service
public class ServiceBImpl implements ServiceB{
    @Autowired
    private EmailUtil emailUtil;

    @Override
    public void sendEmail(args){
    emailUtil.sendEmail(args);
}

Its worth noting that the sendEmail method returns void.

@Component
publIt'sclass EmailUtil{
    @Autowired
    private JavaMailSenderImpl javaMailSenderImpl;

    public void sendEmail(args){
    // send email using spring & other APIs
    }
}

My integration test JUnit looks like this.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test-context.xml" })
public class ServiceIT{
    @Autowired
    private ServiceA serviceA;

    @Test
    public void testA(){
    // prepare data mock in H2 DB
    serviceA.invoke(arg1, arg2);
    // assertions
    }
}

Question (long) - Am using H2 as mock DB so my DAO calls can (need to) function. I want to mock other 3rd party integrations, such as emailUtil.sendEmail call in serviceB so that email is not sent. Since in my JUnit am using serviceA, my understanding is that, I need to create @Mock of EmailUtil and @InjectMocks it into ServiceB which is @InjectMocks into ServiceA. When I do this, Mockito throws errors saying ServiceB is an interface. Creating mock objects of ServiceA, ServiceB may not be a great option, because then I might have to stub/mock too many behaviors. I want the actual logic to be executed in these classes, the mocking should be done only for sendEmail method.

  1. Is this a common problem?
  2. How can I address this?
  3. Is there a problem with the design of this code (i.e. using interfaces and autowiring everything), which makes it difficult to test/mock. If yes, what's a better way to do this?

Spring - 4.0.3.RELEASE, JUnit - 4.12, mockito-core - 1.10.19

Upvotes: 1

Views: 2898

Answers (1)

Ryuzaki L
Ryuzaki L

Reputation: 40058

In the test class ServiceIT mock EmailUtil using @MockBean

Annotation that can be used to add mocks to a Spring ApplicationContext. Can be used as a class level annotation or on fields in either @Configuration classes, or test classes that are @RunWith the SpringRunner.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test-context.xml" })
public class ServiceIT{
@Autowired
private ServiceA serviceA;

@MockBean
private EmailUtil emailUtil;

@Test
public void testA(){
// prepare data mock in H2 DB

//given(this.emailUtil.sendEmail(ArgumentMatchers.any()).willReturn( custom object);
serviceA.invoke(arg1, arg2);
// assertions
     }
 }

Upvotes: 0

Related Questions