Reputation: 43
I am writing unit test for a class A, I want to mock a method but that method is benign called from a class level object, How i will mock this.
Let me explain it from example
Class A which is under test.
public class ClassA {
ClassB objectOfB = new ClassB();
public int add(int a, int b) {
int addition = objectOfB.performCalculation(a,b);
return addition;
}
}
Class B, which has some business logic.
public class ClassB {
public int performCalculation(int a, int b) {
int c = a+b;
System.out.println("I am not mocked, I am actual call");
System.out.println("Returning " + c + " From ClassB");
return c;
}
}
Test Written
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class, ClassB.class})
public class ClassATest {
@InjectMocks
ClassA objA = new ClassA();
@Test
public void testAddFromClassA() throws Exception {
ClassB objB = Mockito.mock(ClassB.class);
Mockito.when(objB.performCalculation(5, 10)).thenReturn(15);
int result = objA.add(5, 10);
assertEquals(result, 15);
}
}
Test Result:
This test is pass, but it is not mocking ClassB's method, instead it is performing actual call.
Requirement:
While writing test, i want to mock line : objectOfB.performCalculation(a,b); from class A, but as you can see object of classB() is created on class level.
How I can mock this?
What i should write in my test class.
Upvotes: 1
Views: 5775
Reputation: 1305
I would suggest reading a little more about mocking and how it is done. Look in the test code you tried to run :
ClassB objB = Mockito.mock(ClassB.class);
Mockito.when(objB.performCalculation(5, 10)).thenReturn(15);
int result = objA.add(5, 10);
assertEquals(result, 15);
What is going on .
You are mocking ClassB but you are not using the mocked instance objB
.
When you are mocking a class, you get back a mocked instance , this doesn't mean the class is now mocked throught your test, it just mean that you can use Mockito to manipulate this specific instace.
What should be going on .
If you can't use that instance to test a method is dependent on it, like in your case, that means you have a very strong dependency between those two classes, and if that is a problam for your tests that usually means your design is faulty. you should be able to inject ClassA into ClassB, either by suppling it an argument in the constructor
public ClassA(ClassB bInstance){
this.bIntance = bInstance
}
or, if that makes since for the specific action, as a function argument.
public add(ClassB classBInstance, int a, int b){
classBInstance. performCalculation(a.b)
}
This will allow you to mock an instance and run ClassA function with the specific implenetation you mocked:
ClassB classBInstance1 = Mockito.mock(ClassB.class)
ClassB classBInstance2 = Mockito.mock(ClassB.class)
Mockito.when(classBInstance1.performCalculation(5, 10)).thenReturn(15);
Mockito.when(classBInstance2.performCalculation(5, 10)).thenReturn(42);
new ClassA(classBInstance1).add(5,10) //returns 15
new ClassA(classBInstance2).add(5,10) //returns 42
new ClassA().add(classBInstance1,5,10) //15
new ClassA().add(classBInstance2,5,10) //42
Upvotes: 0
Reputation: 247571
Mock the initialization of the class so that the mock is used when exercising the test
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class}) //prepare the class creating the new instance of ClassB for test, not the ClassB itself.
public class ClassATest {
@Test
public void testAddFromClassA() throws Exception {
int expected = 15;
ClassB objB = Mockito.mock(ClassB.class);
Mockito.when(objB.performCalculation(5, 10)).thenReturn(expected);
//mocking initialization of ClassB class withing ClassA class
PowerMockito.whenNew(ClassB.class).withNoArguments().thenReturn(objB);
ClassA objA = new ClassA();
//Act
int actual = objA.add(5, 10);
//Assert
assertEquals(expected, actual);
}
}
Now with that said, ideally the target class should follow explicit dependency principle via constructor injection
public class ClassA {
final ClassB objectOfB;
public ClassA(ClassB objectOfB) {
this.objectOfB = objectOfB;
}
public int add(int a, int b) {
int addition = objectOfB.performCalculation(a,b);
return addition;
}
}
The allows the class to explicitly state what it depends on to perform its designed function.
It also allows for inversion of control and loose coupling, which makes the class more flexible to maintain and test
@RunWith(PowerMockRunner.class)
public class ClassATest {
@Test
public void testAddFromClassA() throws Exception {
int expected = 15;
ClassB objB = Mockito.mock(ClassB.class);
Mockito.when(objB.performCalculation(5, 10)).thenReturn(expected);
ClassA objA = new ClassA(objB);
//Act
int actual = objA.add(5, 10);
//Assert
assertEquals(expected, actual);
}
}
Just because PowerMockito allows for the mock construction of new objects does not mean that we should.
If proper design principles are followed, then there really is no need for such hacks.
Upvotes: 1