Issue
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?
Solution
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.
Answered By - Dhawal Kapil