Mockito enables partial mocking of an object, allowing us to create a mock object while still invoking a real method. To achieve this, we can use Mockito’s thenCallRealMethod() method to call a real method on a mocked object.
I have also created several step-by-step video lessons that demonstrate how to test Java applications. If you are interested in learning more, you can check out my video course titled ‘Testing Java with JUnit and Mockito‘.
The Object to Be Mocked
Here is an example of a Plain Old Java Object (POJO) being used to store user details in a database. When working on a test case later, we will mock this object and stub two of its methods, namely getFirstName() and getLastName(). However, the getFullName() method will be called as a real method instead of being stubbed.
package com.appsdeveloperblog.ws.io.entity; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; /** * * @author skargopolov */ @Entity(name = "Profile") public class UserProfileEntity implements Serializable { private static final long serialVersionUID = 7290798953394355234L; @Id @GeneratedValue private long id; private String firstName; private String lastName; private String fullName; /** * @return the firstName */ public String getFirstName() { return firstName; } /** * @param firstName the firstName to set */ public void setFirstName(String firstName) { this.firstName = firstName; } /** * @return the lastName */ public String getLastName() { return lastName; } /** * @param lastName the lastName to set */ public void setLastName(String lastName) { this.lastName = lastName; } /** * @return the id */ public long getId() { return id; } /** * @param id the id to set */ public void setId(long id) { this.id = id; } /** * @return the fullName */ public String getFullName() { if (fullName == null) { fullName = getFirstName() + " " + getLastName(); } return fullName; } /** * @param fullName the fullName to set */ public void setFullName(String fullName) { this.fullName = fullName; } }
Use Mockito to Mock an Object
To mock the UserProfileEntity object, we will annotate it with the @Mock annotation and call the MockitoAnnotations.initMocks(this) method in the setUp() method, as shown below:
@Mock UserProfileEntity userProfileEntity; @Before public void setUp() { MockitoAnnotations.initMocks(this); }
Stubbing Mock Object with Mockito
Now that you have learned how to mock UserProfileEntity, let’s continue and learn how to stub it with predefined values.
// Stubbinb userProfileEntity methods when( userProfileEntity.getFirstName() ).thenReturn( "Sergey" ); when( userProfileEntity.getLastName()).thenReturn( "Kargopolov" ); when( userProfileEntity.getId() ).thenReturn( new Long(1) );
Call a Real Method
In Mockito, to invoke a real method of a mock object, we can use the thenCallRealMethod()
method.
// Call a real method of a Mocked object when( userProfileEntity.getFullName() ).thenCallRealMethod();
Test Class Complete Example
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.appsdeveloperblog.ws.service.impl; import com.appsdeveloperblog.ws.TestConfiguration; import com.appsdeveloperblog.ws.io.dao.Database; import com.appsdeveloperblog.ws.io.entity.UserProfileEntity; import com.appsdeveloperblog.ws.service.UsersService; import com.appsdeveloperblog.ws.shared.dto.UserProfileDto; import org.junit.*; import org.junit.runner.RunWith; import static org.mockito.ArgumentMatchers.any; import org.mockito.InjectMocks; import org.mockito.Mock; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( classes = TestConfiguration.class ) public class UsersServiceImplTest { @Mock Database database; @Mock UserProfileEntity userProfileEntity; @Autowired @InjectMocks @Qualifier("usersService") UsersService usersService; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testSaveUserWithFullName() { //Stubbing Database open and close methods doNothing().when(database).openConnection(); doNothing().when(database).closeConnection(); // Stubbing userProfileEntity methods when( userProfileEntity.getFirstName() ).thenReturn( "Sergey" ); when( userProfileEntity.getLastName()).thenReturn( "Kargopolov" ); when( userProfileEntity.getId() ).thenReturn( new Long(1) ); when( userProfileEntity.getFullName() ).thenCallRealMethod(); // Stubbing database saveUserProfile method when( database.saveUserProfile( any(UserProfileEntity.class) ) ).thenReturn( userProfileEntity ); // Create sample UserProfileDto UserProfileDto userProfileDto = new UserProfileDto(); userProfileDto.setFirstName( "Sergey" ); userProfileDto.setLastName( "Kargopolov" ); userProfileDto.setFullName( "Sergey Kargopolov" ); // Call saveUser method UserProfileDto result = usersService.saveUser( userProfileDto ); // Assert expected results Assert.assertNotNull( result ); Assert.assertEquals( userProfileDto.getFirstName() , result.getFirstName() ); Assert.assertEquals( userProfileDto.getLastName() , result.getLastName() ); Assert.assertNotNull( result.getFullName() ); Assert.assertEquals( userProfileEntity.getFullName() , result.getFullName() ); } }
Handling Exceptions with thenCallRealMethod()
When using Mockito’s thenCallRealMethod()
method, it’s important to consider what will happen if the real method being called throws an exception. If this occurs, the exception will be thrown and propagated up the call stack, just as it would if the method were called normally.
To handle the exception and perform assertions on it, you can use Mockito’s try-catch syntax. Here’s an example:
@Test public void testRealMethodThrowsException() { MyClass myClassMock = Mockito.mock(MyClass.class); Mockito.when(myClassMock.getValue()).thenCallRealMethod(); try { int result = myClassMock.getValue(); fail("Expected an exception to be thrown"); } catch (MyException ex) { assertEquals("Expected exception message", ex.getMessage()); } }
In this example, we’re testing a method called getValue()
on a mocked instance of MyClass
. We’re using thenCallRealMethod()
to call the real implementation of getValue()
, which we know will throw a MyException
under certain circumstances.
To test this behaviour, we use Mockito’s fail()
method to signal that we expect an exception to be thrown. Then, we wrap the call to myClassMock.getValue()
in a try-catch block, catching the expected MyException
and performing assertions on its message.
It’s worth noting that if you’re using thenCallRealMethod()
to call a method that throws a checked exception, you’ll need to add the exception to your test method’s throws clause. For example:
@Test(expected = MyCheckedException.class) public void testRealMethodThrowsCheckedException() throws MyCheckedException { MyClass myClassMock = Mockito.mock(MyClass.class); Mockito.when(myClassMock.getValue()).thenCallRealMethod(); int result = myClassMock.getValue(); }
In this example, we’re using the expected
attribute of the @Test
annotation to specify that we expect a MyCheckedException
to be thrown by the real implementation of getValue()
. We’ve also added throws MyCheckedException
to the method, signature to satisfy Java’s checked exception handling requirements.
By understanding how to handle exceptions when using thenCallRealMethod()
, you can write more robust and effective unit tests that account for unexpected behaviour in your code.
Mockito’s thenCallRealMethod() vs spy() method
Both Mockito’s spy() and thenCallRealMethod() methods can be used to partially mock an object, but they serve different purposes and should be used in different scenarios.
A spy is a real object that is being spied on, with some methods being mocked while others remain unmocked. When using a spy, you can selectively mock methods that you want to change the behaviour of, while other methods will continue to behave normally. This can be useful if you need to test some methods of a complex object while still using the actual implementation of other methods.
On the other hand, thenCallRealMethod()
is used to invoke the real method of a mock object instead of a stubbed method. This is useful when you want to test the actual implementation of a method while still using a mock object. It can be helpful if you want to test a particular method in isolation while still mocking other parts of the code.
In general, you should use a spy when you want to test a specific object with some complex behaviour, but you still want to test the actual implementation of some of its methods. You should use thenCallRealMethod() when testing a single method in isolation while still using a mock object for the other parts of the code.
In summary, a spy should be used when you want to test a specific object that has some complex behaviour but you still want to test the actual implementation of some of its methods. When you want to test a single method in isolation while still using a mock object for the other parts of the code, you should use thenCallRealMethod()
.
Conclusion
In conclusion, Mockito is a powerful mocking framework that allows developers to create mock objects and define their behaviors during testing. One aspect of Mockito is the ability to call a real method on a mock object, which can be useful in certain scenarios.
Unlock the potential of Mockito and enhance your testing prowess. Visit the Testing Java Code page for a wide range of Mockito tutorials that will empower you to write comprehensive tests, mock dependencies effortlessly, and ensure the reliability of your Java applications.
I my case it does not work because the real method call also a injected (mocked) class which is null during test execution.
Is there something more I can do?
I mocked the already the internal injected class in my test.