New issue 304 by kdomb...@gmail.com: Constructor injection with initMocks
fails with TestNG
http://code.google.com/p/mockito/issues/detail?id=304
Since TestNG only instantiates your test class once and not for each test
case like JUnit does, you can not use MockitoAnnotations.initMocks(this)
just by itself with constructor injection.
http://martinfowler.com/bliki/JunitNewInstance.html
http://beust.com/weblog/2004/08/25/testsetup-and-evil-static-methods/
The issue appears to be that FieldInitializer.acquireFieldInstance() checks
to see if the field is null or not. If it is not null then it will
instantiate it otherwise you are left with the instance from the previous
test case.
So if you have a test class that has multiple test cases, the first one
will work as expected and the subsequent tests may get unexpected behavior.
Example with TestNG:
package org.mockitousage.annotation;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockitousage.examples.use.ArticleCalculator;
import org.mockitousage.examples.use.ArticleDatabase;
import org.mockitousage.examples.use.ArticleManager;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
@Test
public class MockInjectionUsingConstructorUsingTestNGTest {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@InjectMocks private ArticleManager articleManager;
@BeforeMethod
public void setup() {
MockitoAnnotations.initMocks(this);
}
public void
initMocks_should_create_all_new_objects_each_time_execution_1() {
articleManager.updateArticleCounters("new");
verify(calculator).countArticles(eq("new"));
}
// this test will fail because the Mocks injected into ArticleManager
will be from the previous test case
public void
initMocks_should_create_all_new_objects_each_time_execution_2() {
articleManager.updateArticleCounters("new");
verify(calculator).countArticles(eq("new"));
}
}
Ideally @InjectMocks would create a new instance each time
MockitoAnnotations.initMocks is called. But this could cause problems for
test cases that inline the field initialization.
Example:
@InjectMocks private ArticleManager articleManager = new ArticleManager();
I can work around the issue by changing the setup to be something like the
following but it would be nice to not have to.
@BeforeMethod
public void setup() {
articleManager = null;
MockitoAnnotations.initMocks(this);
}
Attachments:
testng_with_construtor_injection.patch 3.0 KB
Hi,
Yes we are aware of the different philosophy of JUnit and TestNG, the
curent implementation was a design choice, and is actually expected.
In JUnit this is not a problem since it is running a fresh new instance of
the test between any test methods. The MockitoJUnitRunner helps to also
manage better this redundant part.
However TestNG prefer to keep a unique test instance between test method
runs in the same class, it was decided that we won't break current test
code, such as the one you pointed out, ie : @InjectMocks private
ArticleManager articleManager = new ArticleManager();
And TestNG doesn't have to my knowledge something like @RunWith. So users
are unfortunately stucks to deal with this manually.
On this matter also the following code in your snippet does seem to break
the TestNG philosophy on this matter, ie only one initialization.
@BeforeMethod
public void setup() {
MockitoAnnotations.initMocks(this);
}
It would break anyway if you mess with the state of your production code
between your runs.
While your alternative will work it is so JUnit like, that I think the more
correct way for TestNG (not the most user friendly though) would be
initialize stuff before any test method are executed.
So instead of using @BeforeMethod which will be run before each test method
as the name suggest, you should instead use @BeforeClass whose annotated
method will be run once for the entire class, almost like the JUnit's
@BeforeClass, but it will run on the instance rather than on the class.
So you should write :
@BeforeClass
public void setup() {
MockitoAnnotations.initMocks(this);
}
However this approach has the drawback tht you will need to reset the mocks
after each test methods :
@AfterMethod
public void setup() {
Mockito.reset(mock1, mock2);
}
Makes sense ?
While I don't think it will be fixed, your point raise the fact that
Javadoc could be improved on how to work with this feature and with TestNG.
If you want to avoid writing an @AfterMethod to reset Mockito's state for
each of your test, you should consider doing so in an ITestNGListener.
Hi Cedric,
So it would be possible to configure, via an annotation, an extension to
make all the repetitive work ?
That would be great.
Yes, you could write an ITestListener and do the reset in the
onTestFinished/Passed/Failed callbacks. You can declare this listener
either as an annotation, in your testng.xml or via ServiceLoader (in which
case you don't need to declare it anywhere, having it in your classpath is
all that's needed).
Relevant documentation:
http://testng.org/doc/documentation-main.html#testng-listeners
Comment #5 on issue 304 by brice.du...@gmail.com: Constructor injection
with initMocks fails with TestNG
http://code.google.com/p/mockito/issues/detail?id=304
That's good news. I didn't knew about listeners in TestNG. I will probably
work on a closer Mockito-TestNG integration then.
I think we could then write :
@Listeners({MockitoTestNGListener.class})
public class TheTestNGTest {
// @mock / @injectmocks annotated fields
}
That's good news. I didn't knew about listeners in TestNG. I will probably
work on a better Mockito-TestNG integration then.
For now I think we could write something like :
@Listeners({MockitoTestNGListener.class})
public class TheTestNGTest {
// @mock / @injectmocks annotated fields
// test methods
}
Good point about my example of @BeforeMethod. I guess I was "hacking"
TestNG to behave like JUnit.
The reason I opened this as a bug was because of the inconsistency of my
test results between property vs. constructor injection. If ArticleManager
is written using property injection the unit test above will work but if
you use constructor injection you will get different behaviour.
A IInvokedMethodListener could be created using the afterTest method to
reset all of the Mocks and Spies after each test invocation.
One nice thing that would help would be a Mockito.resetAll() method. Looks
like it has been discussed before.
http://code.google.com/p/mockito/issues/detail?id=119
It doesn't appear that TestNG has a listner interface for the equivalent of
@BeforeClass.
http://groups.google.com/group/testng-dev/browse_thread/thread/d04fb64bf8c156a9/
I looked at IConfigurationListener but it only invokes one of its on*
methods for each @Before* annotated method defined in the test class. So if
you don't have any @Before* methods define in the test class this listener
will not be invoked. So it does not appear to be a good canidate for
initializing all of the mocks.
IObjectFactory could be used to call MockitoAnnotations.initMocks(object);
every time a unit test class is created. Looks like a possible problem with
this solution is that can only have 1 IObjectFactory defined, so if someone
already created one of these this object factory may cause problems.
Another issue that I have noticed with the TestNG @Listeners is that if you
define it in 1 test class it will apply to ALL test classes which may not
be what a person wants. The reason is that Listeners are global.
http://groups.google.com/group/testng-users/browse_thread/thread/fd286e07f3ec3c91/b344a76c389c4bc0?
http://testng.org/doc/documentation-main.html#listeners-testng-xml
One nice thing about using a listener is that you could create a jar file
with your listeners and it automatically gets applied to all tests as long
as you ad the jar to your classpath.
http://testng.org/doc/documentation-main.html#listeners-service-loader
With the issues I have found with listeners the best solution in my case is
to implement an abstract test class that all other unit tests will extend
from and perform the setup and reset like you listed above.
public abstract class BaseMockitoTestCase {
@BeforeClass
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@AfterMethod
public void resetMocks() {
for (Field field : this.getClass().getDeclaredFields()) {
Object property = Whitebox.getInternalState(this,
field.getName());
if (new MockUtil().isMock(property)) {
Mockito.reset(property);
}
}
}
}