Is it discouraged to use @Spy and @InjectMocks on the same field?
@Spy and @InjectMocks cannot be used well together (see Google Code issue #489 and GitHub issue #169), and for what they do it is not clear or common that they should be used together at all. In well-written Mockito usage, you generally should not even want to apply them to the same object.
@InjectMocks works as a sort of stand-in dependency injection for the system under test: If you have a test that defines a @Mock or @Spy of the right type, Mockito will initialize any fields in your @InjectMocks instance with the contents of those test fields. This might be handy if you haven't otherwise structured your system-under-test for dependency injection (or if you use a DI framework that does field injection) and you want to replace those dependencies with mocks. It can be pretty fragile—unmatched fields will be silently ignored and will remain
null
if not set in an initializer—but remains a decent annotation for your system under test.@Spy, like @Mock, is designed to set up test doubles; you should use it when you have a collaborator that you want to stub or verify. Though there are cases where you could spy on your system under test, @Spy and @Mock are meant for dependencies, and not for the functionality you are testing.
Ideally, you should not have any class that fulfills both roles in the same test, or else you may find yourself writing a test that painstakingly tests behavior that you've stubbed rather than actual production behavior. In any case it will be more difficult to tell exactly what the test covers versus the behavior you've stubbed.
Of course, this may not apply if you're trying to use Mockito to test a single method in isolation, and you want to stub calls to one method while testing the other. This can be accomplished by creating a spy of your system-under-test and mocking some of its methods to avoid calling collaborators. However, this might also be an indication that your class is violating the Single Responsibility Principle, and that you should break down the class into multiple independent classes that work together. Then, in your test, you can allow instances to have exactly one role and never need both annotations at once.
Multiple levels of @Mock and @InjectMocks
Since I didn't get any response here I asked on the Mockito forums. Here is a link to the discussion: https://groups.google.com/d/topic/mockito/hWwcI5UHFi0/discussion
To summarize the answers, technically this would kind of defeat the purpose of mocking. You should really only mock the objects needed by the SystemUnderTest class. Mocking things within objects that are themselves mocks is kind of pointless.
If you really wanted to do it, @spy can help
How can I mock methods of @InjectMocks class?
First of all the reason for mocking MyHandler methods can be the following: we already test anotherMethod()
and it has complex logic, so why do we need to test it again (like a part of someMethod()
) if we can just verify
that it's calling?
We can do it through:
@RunWith(MockitoJUnitRunner.class)
class MyHandlerTest {
@Spy
@InjectMocks
private MyHandler myHandler;
@Mock
private MyDependency myDependency;
@Test
public void testSomeMethod() {
doReturn(1).when(myHandler).anotherMethod();
assertEquals(myHandler.someMethod() == 1);
verify(myHandler, times(1)).anotherMethod();
}
}
Note: in case of 'spying' object we need to use doReturn
instead of thenReturn
(little explanation is here)
How to stub a method of an class annotated with @InjectMocks?
I am really surprised that you do not get a MissingMethodInvocationException
which is thrown when you try to stub a method of an object not being a @Mock or a @Spy.
The dictionary
instance is just a regular instance of a class here not proxied by Mockito (because of the fact that only @InjectMocks annotation is used).
Another surprise is that you do not get a null
when the map.get
is triggered as default return value for a String returning method is null
.
Anyway..
If you want to stub methods of the `dictionary' instance you have to configure your test class as follows:
@InjectMocks
@Spy
MyDictionary dictionary;
@Test
public void testMyDictionary(){
doReturn("value").when(dictionary).get("key);
Assert.assertEquals("value", dictionary.get("key"));
}
Using @InjectMocks to replace @Autowired field with a mocked implementation
Do you have to use Mockito annotations to setup dependencies for the class under test?
If that is not the main constraint why not just do the plain simple setup and introduce a constructor or a setter in ServiceIWantToTestImpl
class for the ComplicatedDependency
field and set the dependency in your test setup directly to whatever impl of ComplicatedDependency you like e.g.:
@Service
public class ServiceIWantToTestImpl implements ServiceIWantToTest {
@Autowired
ComplicatedDependency complicatedDependency;
public ServiceIWantToTestImpl() {
}
public ServiceIWantToTestImpl(ComplicatedDependency complicatedDependency) {
this.complicatedDependency = complicatedDependency;
}
@Override
public void methodUsingDependency(){
String string = complicatedDependency.doSomething();
System.out.println(string);
}
}
public class TestingTheService {
private static ServiceIWantToTestImpl serviceIWantToTest;
@BeforeClass
public static void init(){
serviceIWantToTest = new ServiceIWantToTestImpl(new MockComplicatedDependency());
}
@Test
public void testAttempt() {
serviceIWantToTest.methodUsingDependency();
}
}
That is one way.
To make it work with Mockito, You could to use @Spy instead of @Mock like this:
@RunWith(MockitoJUnitRunner.class)
public class TestingTheService {
@InjectMocks
private static ServiceIWantToTestImpl serviceIWantToTest = new ServiceIWantToTestImpl();
@Spy
private static ComplicatedDependency complicatedDependency = new MockComplicatedDependency();
@BeforeClass
public static void init() {
}
@Test
public void testAttempt() {
serviceIWantToTest.methodUsingDependency();
}
}
Though this is a bit of a hack. I strongly recommend that you read the JavaDoc of the @Spy annotation and make sure it's expected use is what you really need for your test.
Related Topics
How to Invalidate an User Session When He Logs Twice with the Same Credentials
How to Get an Enum Based on the Value of Its Field
Java:Does Wait() Release Lock from Synchronized Block
Is There a Java Equivalent to C#'s 'Yield' Keyword
Other Ways of Singleton in Java
Fastest Way to Iterate an Array in Java: Loop Variable VS Enhanced for Statement
Is There a Method That Calculates a Factorial in Java
Selenium Webdriver Submit() VS Click()
Download Large File from Server Using Rest Template Java Spring MVC
How to Split Array List into Equal Parts
How to Do Query Auto-Completion/Suggestions in Lucene
JSON Gson.Fromjson Java Objects
Capture Generated Dynamic Content at Server Side
"Faceted Project Problem (Java Version Mismatch)" Error Message
How to Configure Log4J to Log Different Log Levels to Different Files for the Same Logger