2

We are currently using JUnit and Mockito to test our code.

We have a Service Interface something like below.

public interface ServiceA {
     void doSomething();
}

And its implementation class is like below.

@Service
@Transactional
public class ServiceAImpl implements ServiceA {

     @Inject
     private RepositoryA repA;

     @Inject
     private ShareServiceA sharedServA;

     public void doSomething(){
     }
}

Now, I would just like to mock repA dependency of ServiceAImpl class.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:test-context.xml" })
@DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS)
public class ServiceAImplTest {

   @Mock
   RepositoryA repA;

   @InjectMocks
   ServiceA servA = new ServiceAImpl();

   @Before
   public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        ........
   }
}

After calling initMocks, only repA dependency of ServiceImpl was initiated and sharedServA remains null thus causing null exception when the class being tested calls a method of sharedServA.

Based on some references that I've read on the internet and books, this will only happen if the class being tested has constructor with arguments declared. But, for my example above, I didn't declare any constructor. Is this the correct behavior or am I missing something?

artsylar
  • 2,648
  • 4
  • 34
  • 51

5 Answers5

3

You're missing something, and that's the mocking of the service:

@Mock
private ShareServiceA sharedServA;

in your test class. The

@Mock
RepositoryA repA;

is the reason your mock repository is injected.

EDIT:

Since you "don't want to mock your sharedServA", maybe you want to

@Spy
private ServiceAImpl sharedServA;

but that's usually bad test style - the Mockito docs state that:

Real spies should be used carefully and occasionally, for example when dealing with legacy code.

daniu
  • 14,137
  • 4
  • 32
  • 53
  • Thank you for your answer. But, I don't want to mock sharedServA. I only want to mock repA dependency. – artsylar May 11 '18 at 06:27
  • @artsylar Then your sharedServA can only stay null, as it is. What do you want it to be? If you need an actual instance of the service, you can use `@Spy`. I edited my answer. – daniu May 11 '18 at 06:32
  • I see. So if you have lots of dependencies within a class, you still need to declare all of them using @Spy within the test class even if you will not be mocking any of its method inside the test class. – artsylar May 11 '18 at 06:42
  • 1
    @artsylar Well I guess the TL;DR is "if you don't assign anything to a variable, it's `null`" (which is what you're seeing right now) so you do need to assign something. Either a mock, a spy, or a real instantiated object. Mock is the best choice. – daniu May 11 '18 at 06:47
3

Continuing to @daniu answer

You are stuck because of a mixed-match approach.

If you are writing test for a unit you will have to satisfy dependencies of the unit in your test also.

Let's consider you have a class A with two dependencies B and C and your are injecting them old-fashion using setters like

public class A {

    private B b;

    private C c;

    void method() {
        System.out.println("A's method called");
        b.method();
        c.method();

    }

    public void setB(B b) {
        this.b = b;
    }

    public void setC(C c) {
        this.c = c;
    }
}

Then unit test for above class will be like

public class ATest {

    A a=new A();

    @Test
    public void test1() {
        B b=new B();
        C c=new C();
        a.setB(b);
        a.setC(c);

        a.method();
    }
}

dependencies satisfied in test also using setter injection. This was actually the way how java units were tested prior to mockito framework. Here B and C could have been test-doubles or actual classes as per need.

But if we are using annotation based dependency injection in our classes using spring then our A class will look something like

@Service
public class A {

    @Inject
    private B b;

    @Inject
    private C c;

    void method() {
        System.out.println("A's method called");
        b.method();
        c.method();

    }
}

Here we are relying upon @Inject to inject our dependency automatically using spring configurations.

But to unit test these we need some framework such as mockito which is equally capable of doing so i.e. injecting dependency without setter or constructor. Every dependency can be either a mock or a spy(if actual invocation is needed)

Hence test of A should look like

@RunWith(MockitoJUnitRunner.class)
public class ATest {

    @InjectMocks
    A a;

    @Mock //or @Spy
    B b;

    @Mock //or @Spy
    C c;

    @Test
    public void test() {
        a.method();
    }
}

But you can not mix and match. If you do not want to use @InjectMocks and not want to @Spy for every dependency that you want to be actually executed simply @Autowire or instantiate A in your class that will load A with all the actual dependencies but then you will have to think how to override that one or two specific mocks that you want to be mocked.

1) You can provide a setter for that specific dependency and then you can create a mock and insert it.

public class ATest {

    @Autowired
    A a;

    @Mock
    B b;

    @Test
    public void test() {
        a.setB(b);
        a.method();
    }
}

2) You can be a badass and use ReflectionTestUtils to injected mocked dependencies on the fly even if your original class does not provide a setter for it.

@ContextConfiguration(classes=Config.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ATest {

    @Autowired
    A a;

    @Mock
    B b;

    @Test
    public void test() {
        ReflectionTestUtils.setField(a, "b", b);
        a.method();
    }
}

3) Also as mentioned by @Yogesh Badke you can maintain such mocked beans in a separate test context file and use that but again that file will have to be maintained for every such specific example.

Dhawal Kapil
  • 2,584
  • 18
  • 31
  • Thank you very much for your detailed explanation. Actually, I also tried Solution 2 but it didn't work. I'm getting "Could not find "b" of type [null] on target...." – artsylar May 11 '18 at 10:45
  • 1
    your bean `A` is marked `@Transactional` which may be causing this further issue. Please see this post https://stackoverflow.com/a/38344022/2179336 this should help. – Dhawal Kapil May 11 '18 at 10:54
  • 1
    As per the post Spring >4.3.1 should work fine else this solution can be used https://stackoverflow.com/a/34477414/2179336 – Dhawal Kapil May 11 '18 at 10:55
  • 1
    Unfortunately, we are using 4.2. But it is okay, I was able to find a workaround to make RefectionTestUtils.setField() to work. – artsylar May 11 '18 at 11:31
2

As you want to mock one bean but want to have the real instance of another bean, you would need to load whole spring context so that the real bean is injected properly, and you have to register mocked bean into spring context so that it gets injected.

So, in one of your test configuration classes declare a mocked bean of RepositoryA.

@Bean
public void mockedRepositoryA() {
   return mock(RepositoryA.class);
}

OR in the test-context.xml as follow

<bean id="idOfServiceAHere" class="org.mockito.Mockito" factory-method="mock">
</bean>

and then inject it as if it's the real bean via @Inject annotation.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:test-context.xml" })
@DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS)
public class ServiceAImplTest {

   // internally, you will have mocked instance of RepositoryA and
   // real instance of ShareServiceA.
   @Inject
   private ServiceA serviceA;

   @Before
   public void setUp() throws Exception {
        ....
   }
}
Yogesh Badke
  • 4,249
  • 2
  • 15
  • 23
1
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:test-context.xml" })
@DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS)
public class ServiceAImplTest {

   @Mock // not @mock
   RepositoryA repA;

   // need to mock each dependency of class under test
   @Mock
   ShareServiceA sharedServA;

   // no need to initialize via new
   @InjectMocks
   ServiceA servA;

   @Before
   public void setUp() throws Exception {
        // no need for this,
        // @RunWith(SpringJUnit4ClassRunner.class) will be enough
        // MockitoAnnotations.initMocks(this);
        ........
   }
}

See comments in the code.

In case you don't want to mock ShareServiceA, you can still mock it and call real method instead of returning mock result.

Mockito.when(sharedServA.someMethod(any())).thenCallRealMethod();
Hemant Patel
  • 3,160
  • 1
  • 20
  • 29
0

You should create @Mock for ShareServiceA sharedServA.
because any object in java will create a constructor default.

If ok. Please give your code about ShareServiceA.

Tom Nguyen
  • 67
  • 6
  • Thank you for your answer. But, I don't want to mock sharedServA. I only want to mock repA dependency. – artsylar May 11 '18 at 06:27
  • 2
    You can try add bean ShareServiceA with name sharedServA in test-context.xml. Spring auto check bean in test-context and auto inject to ServiceA. – Tom Nguyen May 11 '18 at 06:41