0

Is it possible to mock an object with Mockito which is called somewhere in the object hierarchy?

Let's assume following classes:

@Component
Class A
    @Autowired B b

    method obtainSalaryA()
        calls b.calculateSalary() 

@Component
Class B
    @Autowired C c

    method calculateSalary()
        calls c.getDatabaseSalary()

@Component
Class C

    getDatabaseSalary()

In the unit test, I would like to unit test the method obtainSalary from class A, but mock C at the same time, so that I replace the return value of getDatabaseSalary() when this method is called in the hierarchy.

Chiseled
  • 2,280
  • 8
  • 33
  • 59
Chris
  • 89
  • 1
  • 10

3 Answers3

1

Actually if you test 'a' you mock class 'b' and you can return whatever you need from b. And if you test 'b' you mock 'c'. Try always to test without dependencies (unit tests)

@RunWith(MockitoJUnitRunner.class)
 public class ClinicServiceImplTests {
    @Mock private B b;
    @InjectMocks private A a;


   @Before
    public void setUp() throws Exception {
        doReturn(...).when(b).someMethod ();
    }
}
0

I haven't tried this but see if it works, or some variation of it.

class YourTestClass {

    @Mock(answer = Answers.RETURNS_SMART_NULLS)
    C c;

    @InjectMocks
    B b;

    @InjectMocks
    A a;
}

Then in your test, do a when..thenReturn for the c.getDatabaseSalary method.

Also see Difference between @Mock and @InjectMocks.

Community
  • 1
  • 1
Kesh
  • 1,077
  • 2
  • 11
  • 20
0

You can do it with some modification to your design.

Edit: based on your comment, you're working on an integration test. The test would look something like this.

@Autowired
private A a;

@Autowired
private B b;

@Mock
private C c;

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

@Test
public void test()  {
    b.setC(c);
    when(c.getDatabaseSalary()).thenReturn(...);
    a.obtainSalaryA();
}
Khalid
  • 2,212
  • 11
  • 12
  • Thanks a lot. Your suggestions focus mainly on Class A that is tested, all other dependencies are mocked. How would you design the test, when it is rather an integration test where the functionalities of A and B should be tested and for example, the method in class C should deliver other return values in the test than in production? Is the only option to use here ActiveProfiles of Spring to inject another implementation of Class C in the test and do not use mocks? – Chris Nov 04 '14 at 21:40
  • You still can use mocks. See my updated answer. I don't have Spring setup right now, so let me know if it works. – Khalid Nov 04 '14 at 22:14
  • I don't think it work. Autowired ist not a Mockito annotation and 'a' will be null. If an integration test is intended the question should be changed – Kescha Skywalker Nov 05 '14 at 07:27
  • @Kescha That's not a problem. Spring integration tests are wired with Spring and run with `SpringJUnit4ClassRunner.class`. `@Autowired` will be injected. – Khalid Nov 05 '14 at 07:32
  • Than you miss a runwith :) I'm not sure if such mixing of frameworks should be done. Look here http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/ – Kescha Skywalker Nov 05 '14 at 07:38
  • This is only a snippet, I didn't include the class declaration, but I assumed that the OP is running a traditional Spring integration test. Thanks for the link, but the setup used is different. I'll try to test this, I'm interested to see the results. – Khalid Nov 05 '14 at 07:53
  • @Kescha, `@InjectMocks` doesn't work with an `@Autowired` field. I updated the code to inject the mock manually after autowiring. It's working now. Thanks for the heads up. – Khalid Nov 05 '14 at 08:32
  • @Khalid, I have run a test based on your suggestion. However, it seems that the mock C is ignored. So within B, when I call c.getDatabaseSalary I do not get the mocked data. I am injecting C in B through Autowired in the constructor, not as a method parameter. By the way, I am using SpringJUnit4ClassRunner. – Chris Nov 05 '14 at 18:21
  • Are you sure you included `b.setC(c);`? You have to set the `C` in `B` to the mock you just created. – Khalid Nov 05 '14 at 18:23
  • I am injecting C in B through Autowired in the constructor of class B, not as a method parameter. – Chris Nov 05 '14 at 18:23
  • I see. If possible, you can add a setter. The problem is that `C` is autowired in `B` by Spring, so you have to change that to your mock after `B` is created. – Khalid Nov 05 '14 at 18:27
  • With the setter it works, thanks! However, I will rather use a specific implementation of C and use different Spring profiles in my case. – Chris Nov 05 '14 at 18:55