We are writing JUnit tests for a class that uses Spring autowiring to inject a dependency which is some instance of an interface. Since the class under test never explicitly instantiates the dependency or has it passed in a constructor, it appears that JMockit doesn't feel obligated to instantiate it either.
Up until now we have been using SpringRunner to have Spring load mock dependencies for us, which works. Two things we don't like about this are 1) the Spring framework has to be loaded and initialized each time running the tests which is not exactly speedy, and 2) we are forced to explicitly create all mock dependencies as real classes, something which JMockit helps eliminate.
Here's a simplified example of what we're testing:
public class UnitUnderTest {
#Autowired
ISomeInterface someInterface;
public void callInterfaceMethod() {
System.out.println( "UnitUnderTest.callInterfaceMethod calling someInterface.doSomething");
someInterface.doSomething();
}
}
So, the question is, is there a way to have JMockit create a mock someInterface?
JMockit will always instantiate a mocked interface (except in the case of a final mock field), but that only occurs in test code. It will not automatically inject the instance into code under test.
You would have to manually inject the mock instance. For example:
public class SomeTest
{
#Autowired UnitUnderTest unitUnderTest;
#Mocked ISomeInterface theMock; // created and assigned automatically
#Test
public void testSomeMethod()
{
Deencapsulation.setField(unitUnderTest, theMock);
//proceed with unit test here
}
}
mockit.Deencapsulation is a Reflection-based utility class that lets you invoke private methods, get/set fields, etc.
You can use org.springframework.test.util.ReflectionTestUtils to explicitly inject your mocked ISomeInterface in your test case.
See documentation
With the hints kindly provided above, here's what I found most useful as someone pretty new to JMockit: JMockit provides the Deencapsulation class to allow you to set the values of private dependent fields (no need to drag the Spring libraries in), and the MockUp class that allows you to explicitly create an implementation of an interface and mock one or more methods of the interface. Here's how I ended up solving this particular case:
#Before
public void setUp() {
IMarketMakerDal theMock = new MockUp <IMarketMakerDal>() {
#Mock
MarketMakerDcl findByMarketMakerGuid( String marketMakerGuid ) {
MarketMakerDcl marketMakerDcl = new MarketMakerDcl();
marketMakerDcl.setBaseCurrencyCode( CURRENCY_CODE_US_DOLLAR );
return marketMakerDcl;
}
}.getMockInstance();
setField( unitUnderTest, theMock );
}
Thanks everyone for the help.
For those people who met
java.lang.IllegalStateException: Missing #Injectable for field ***
or
java.lang.IllegalStateException: Missing #Tested class for field ***
error when using jmockit to mock #autowired field in spring ( or spring boot) framework, I did below two steps to avoid above errors:
use #Tested(fullyInitialized=true) instead of #Tested
https://groups.google.com/forum/#!msg/jmockit-users/uo0S51lSX24/lQhLNN--eJcJ
revert jmockit's version back to 1.18 or previous ones
https://groups.google.com/forum/#!topic/jmockit-users/wMFZggsA8LM
If you have a #Qualifier annotation for the interface, you need to name your #Injectable field exactly as it is named in qualifier.
Here is quote from JMockit doc:
Custom names specified in field annotations from Java EE (#Resource(name), #Named) or the Spring framework (#Qualifier) are used when looking for a matching #Injectable or #Tested value. When such a name contains a - (dash) or . (dot) character, the corresponding camel-cased name is used instead.
For example:
#Component
public class AClass {
#Autowired
private Bean1 bean1;
#Autowired
#Qualifier("my-dashed-name")
private AmqpTemplate rpcTemplate;
}
Unit test class:
public class AClassTest {
#Injectable
private Bean1 bean1;
#Injectable
private AmqpTemplate myDashedName;
#Tested
private AClass aClass = new AClass();
}
Also there is no need to use setFiled for each #Autowired bean, all fields injects automatically when #Tested class instantiated. Tested on JMockit ver. 1.30
Related
I'm having a situation in which I have certain mocking and utilities (not static methods, but certain manipulations relying on mocks).
Lets say like this,
class MyReusableClassForTesting {
#Mocked
private ClassA attribute;
// And some more #Mocked and methods that are relying on the mocks.
}
I want to use the instance of MyReusableClass in my test classes with mocks injected.
class MyTestClass {
// Need this be a real instance with mocks injected inside it
private MyReusableClassForTesting instance;
}
I tried with #Mocked, #Capturing, #Tested and #Injectable. None of them seem to work. Any idea, how can I make this work with Jmockit?
You would normally just do this:
class MyTestClass {
// Need this be a real instance with mocks injected inside it
#Tested
public MyReusableClassForTesting instance;
}
If the 'instance' constructor takes arguments, or if it has things Autowired, then add (possibly multiple) at the test-class level:
#Injectable
protected ClassA attribute;
To make sure JMockit is working, I usually add a simple test
#Test
public void testCtor() {
assertNotNull(instance);
}
JMockit will take care of creating the 'instance' based on #Tested and #Injectable. The only way this test fails is if JMockit isn't working - i.e. you forgot to add the javaagent. You generally need the javaagent in BOTH your build script (build.gradle or pom.xml) as well as when you run the test manually from your IDE.
I am using JDK 11 and spring boot.
I am implementing a rest API and have 3 layers:
controller
service layer
data access layer
I had classes against interfaces at the data-access-layer and did not have any interface at the service layer.
I wrote integration tests using MockMvc, Mockito, etc to exercise the whole path for each point, exposed by the controller. This was not a problem until I tried to introduce the interface at the service layer.
Initially, I mocked only repositories/Daos. So the class structure looked like:
public interface ClientRepo{
......
}
public class ClientRepoImpl implements ClientRepo{
......
}
Mocked the returned data as:
#MockBean
private ClientRepo client;
....
Mockito.when(client.isExistFkUnitId(Mockito.any(UUID.class))).thenReturn(false);
Everything was fine so far.
Now I have introduced interface at the service layer as :
public interface ClientService{
......
}
public class ClientServiceImpl implements ClientService{
......
}
And tried ( Trying to call actual service method):
#MockBean
private ClientService clientService;
....
Mockito.when(clientService.isExistFkUnitId(Mockito.any())).thenCallRealMethod();
But getting nothing but null all the time.
Is there a way to make the real method call keeping the interface?
I think you want to use #Spy annotation instead of #Mock annotation on the field where you want to call the real method. I don't happen to have an example to verify this though.
https://javadoc.io/doc/org.mockito/mockito-core/2.21.0/org/mockito/Spy.html
Then you can do doCallRealMethod().when(clientService.isExistFkUnitId(Mockito.any())).
Because with a spy object you call doReturn/when instead of when/doReturn.
https://javadoc.io/doc/org.mockito/mockito-core/2.21.0/org/mockito/Mockito.html#do_family_methods_stubs
Well, there is no "real" method to call. (Ignoring the fact that default methods in interfaces are a thing nowadays)
Generally, unit tests should be written for the target class in an isolated fashion. Like this, you are always "testing" the "isExistFkUnitId" method as well.
You could set the mock up for specific values:
Mockito.when(clientService.isExistFkUnitId("valueA").thenReturn("answerA");
Mockito.when(clientService.isExistFkUnitId("valueB").thenReturn("answerB");
Anyways... to respond to your actual question:
If possible, you can instantiate the implementation in a way that the desired method is working and call it through the mock:
ClientServiceImpl clientServiceImpl = new ClientServiceImpl(...);
// spaghetti code only for demonstration purposes ;)
Mockito.when(clientService.isExistFkUnitId(Mockito.any())).then(i -> clientServiceImpl.isExistFkUnitId((String) i.getArguments()[0]));
POC test:
#Test
public void testit() {
Myclass myclass = new Myclass();
Myinterface mock = Mockito.mock(Myinterface.class);
Mockito.when(mock.myMethod(Mockito.any())).then(i -> myclass.myMethod((String) i.getArguments()[0]));
assertThat(mock.myMethod(" works")).isEqualTo("yeehaa works");
}
public interface Myinterface {
String myMethod(String params);
}
public static class Myclass implements Myinterface {
#Override
public String myMethod(String params) {
return "yeehaa" + params;
}
}
Not exactly a beautiful solution, but if there is no way around it, it should work.
As you are mocking an interface Mockito doesn't know which implementation are you referring. The only way will be to use the Class.
I was having the same problem. My problem was due to the ClientService having dependencies that were not mocked when I set up the tests in this format. So ClientService had a mock, but if I tried clientService.productService.get() or something of that nature the dependant productService was always null. I solved this using testing reflection:
#MockBean
DependentService mockDependentService
ControllerToTest controllerToTest
#BeforeEach
public void setup() {
mockDependentService = mock(DependentService.class);
controllerToTest = mock(ControllerToTest.class);
ReflectionTestUtils.setField(controllerToTest, "dependantService", mockDependentService);
}
#Test
void test() {
//set up test and other mocks
//be sure to implement the below code that will call the real method that you are wanting to test
when(controllerToTest.methodToTest()).thenCallRealMethod();
//assertions
}
Note that "dependantService" needs to match whatever you have named the instance of the service on your controller. If that doesn't match the reflection will not find it and inject the mock for you.
This approach allows all the methods on the controller to be mocked by default, then you can specifically call out which method you want to use the real one. Then use the reflection to set any dependencies needed with the respective mock objects.
Hope this helps!
I Have no access to MyClass2 code and can't change it. How do i mock/instantiate MyClass2 myClass2?
Classes and code test:
#RunWith(JUnit4.class)//can't change thisone
public class MyTest
{
#Autowired // or not, tried both ways
MyClass testedInstance= new MyClass();
#Test
public void boot() throws Exception{
testedInstance.boot();
assertTrue(true);
}
}
public class MyClass
{
#Autowired
private MyClass2 myClass2;
void boot()
{
myClass2.foo();//getting a null pointer here
}
}
you can check the original answer
https://stackoverflow.com/a/71591567/5108695
But this is very common problem so I posted the answer here also
In my opinion, we are writing unit test cases and we should not initialize the spring context in order to test a piece of code.
So,
I used Mockito to mock the Autowired beans in my main target test class and injected those mock beans in my main test class Object
maybe sounds confusing, see the following example 💥
Dependencies I used
testImplementation("org.mockito:mockito-core:2.28.2")
testImplementation("org.mockito:mockito-inline:2.13.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.8.2")
testImplementation("org.mockito:mockito-junit-jupiter:4.0.0")
My main class is Maths and Calculator bean is autowired
class Maths{
#Autowired Calculator cal;
.........
.........
public void randomAddMethod(){
cal.addTwoNumbers(1,2); // will return 3;
}
}
Test class
#ExtendWith(MockitoExtension.class)
class MathsTest{
#Mock(answer = Answers.RETURNS_DEEP_STUBS) Calculator cal;
#InjectMocks Maths maths = new Maths();
#Test testMethodToCheckCalObjectIsNotNull(){
maths.randomAddMethod();
}
}
Now cal will not be null in Maths class and will work as expected
First you need to annotate your test class with:
#RunWith( SpringJUnit4ClassRunner.class )
Then about MyClass:
#Autowired
MyClass testedInstance;
you need to delete the = new MyClass(); because you are autowiring it.
Then since now you are injecting MyClass if there is available for injection instance of MyClass2 it will be injected in MyClass, if not it will not.
You need to configure your application context so that such bean MyClass2 exists,
Something that can bite is if you're using the implementation class for autowiring purposes in tests, but the operational code is using an interface instead and there is proxying. This can happen if, for example, you are using #PreAuthorize annotations from Spring Security, or pointcuts from Spring AOP. I'm not 100% sure if this happens with CGLIB proxies, but it definitely happens with JDK proxies.
You have to get exactly the correct bean or things will not work and you will get very strange ("impossible") null pointer exceptions due to the bean you're testing being in a half-configured state.
I found this article quite interesting http://www.jayway.com/2012/02/25/mockito-and-dependency-injection/ it says that Mockito supports dependency injection by using constructor arguments, setter methods and field injection. I was wondering if JMockit does the same, so far I haven't find no one using JMockit and dependency injection.
JMockit supports dependency injection via arguments and properties. The test class must contain one or more mock properties or mock parameters declared to be #Injectable. The business object you would like to test need to be declared with the annotation #Tested. The #Tested annotation automatically creates an instance of the class and injecting the mocked dependencies.
public class SomeTest {
#Tested CodeUnderTest tested;
#Injectable Dependency dep1;
#Injectable AnotherDependency dep2;
#Injectable int someIntegralProperty = 123;
#Test
public void someTestMethod(#Injectable("true") boolean flag, #Injectable("Mary") String name)
{
// Record expectations on mocked types, if needed.
tested.exerciseCodeUnderTest();
// Verify expectations on mocked types, if required.
}
}
You can find more detailed information here:
http://jmockit.github.io/tutorial/BehaviorBasedTesting.html#tested (official docs)
In a JUnit test, I want to change the hibernate template in a Spring DAO. This DAO is
annotated with #Transactional so it gets wrapped during runtime and
spyed upon by Mockitos spy()- method. So the DAO will be wrapped a second time by that spy.
So the DAO now has two wrapping objects: One from #Transactional, one from the spy. Due to the fact that it's not known which of those wrapper is created first, I can't set the hibernate template in the DAO via reflection.
How can I set the template in the doubled-wrapped DAO?
[Edit]
Some Source:
/**
* This class gets wrapped by a proxy object because of #Transactional.
*/
#Transactional
public class MyDao implements SomeDaoInterface { ... }
In a test class:
public class MyTestClass {
#Autowired
private MyDao myDao;
#Test
public void myTestMethod() throws Exception {
final MyDao daoSpy = spy(myDao); // Dao gets wrapped with second wrapper
final Field field = MyDao.class.getDeclaredField("template");
field.setAccessible(true);
field.set(daoSpy, mySpecialMockedTemplate); // ERROR: want to inject the template but
// dont know in which wrapper
}
}
Call the setter method instead of accessing the field.
It seems you reflection code is wrong. Use this statement instead :
field.set(daoSpy, mySpecialMockedTemplate);
However looking at your test code, it seems you are using Spring to create the MyDao instance. It seems kind of weird to use reflection to set the template, why not configure it in Spring ?
Or even use an actual setter ? Or make the field protected, so only the unit test can access it.
EDIT : About injection, you could create the DAO instance in your test and have your specialMockedTemplate injected by Mockito. You could writ something like :
#RunWith(MockitoJUnitRunner.class)
public class MyTestClass {
#InjectMocks private MyDao dao;
#Mock SpecialTemplate specialTemplate;
#Test void dao_should_call_the_template_with_parameter_A_and_B() {
// given
// when
dao.someCall("A", "B");
// then
verify(specialTemplate).someCallWith("A", "B");
}
}
A few warnings though, avoid partial mocking if possible (using spies). Avoid moking types you don't own, you should read this blog post entry why this is a bad idea.