Mock Doc
Mocks are simulated objects that mimic the behavior of real objects in controlled ways. OpenMRS 1.9 finally includes Mockito which is a library designated for creating mocks dynamically. In this document we will present how to use them in the context of OpenMRS. The presented code works in OpenMRS 1.9.9+.
It is best illustrated by an example. Let's rewrite org.openmrs.EncounterTest.setProvider_shouldSetExistingProviderForUnknownRole() using mocks. The test makes sure that a provider with the unknown role will be set for the given person when calling org.openmrs.Encounter.setProvider(Person).
/**
* @see Encounter#setProvider(Person)
* @verifies set existing provider for unknown role
*/
@Test
public void setProvider_shouldSetExistingProviderForUnknownRole() throws Exception {
//given
Encounter encounter = new Encounter();
Person person = Context.getPersonService().getPerson(1);
Collection<Provider> providers = Context.getProviderService().getProvidersByPerson(person);
EncounterRole role = Context.getEncounterService().getEncounterRoleByUuid(EncounterRole.UNKNOWN_ENCOUNTER_ROLE_UUID);
Assert.assertNotNull("Unknown role", role);
//when
encounter.setProvider(person);
//then
Assert.assertEquals(1, encounter.getProvidersByRole(role).size());
Assert.assertTrue(encounter.getProvidersByRole(role).contains(providers.iterator().next()));
}
The problem with this test is that Encounter.setProvider(Person) calls EncounterService.getEncounterRoleByUuid(String) and ProviderService.getProvidersByPerson(Person), both of which require the presence of the Spring Application Context and the database running. In the effect we are forced to write a heavy component test rather than a quick unit test. Mocks will allow us to go around this issue by stubbing the two methods.
First, we will modify the class declaration and members. We will get rid of BaseContextSensitiveTest which is responsible for setting up the Spring Application Context and the database. We will extend BaseContextMockTest instead, which provides support for @Mock annotations. We will also mock EncounterService and ProviderService by adding them as private members with @Mock annotation.
public class EncounterTest extends BaseContextMockTest {
@Mock
private EncounterService encounterService;
@Mock
private ProviderService providerService;
Finally, we will change the test to use mocked services.
/**
* @see Encounter#setProvider(Person)
* @verifies set existing provider for unknown role
*/
@Test
public void setProvider_shouldSetExistingProviderForUnknownRole() throws Exception {
//given
Encounter encounter = new Encounter();
EncounterRole unknownRole = new EncounterRole();
Person person = new Person();
Provider provider = new Provider();
provider.setPerson(person);
List<Provider> providers = new ArrayList<Provider>();
providers.add(provider);
when(encounterService.getEncounterRoleByUuid(EncounterRole.UNKNOWN_ENCOUNTER_ROLE_UUID)).thenReturn(unknownRole);
when(providerService.getProvidersByPerson(person)).thenReturn(providers);
//when
encounter.setProvider(unknownRole, provider);
//then
assertEquals(1, encounter.getProvidersByRoles().size());
assertEquals(1, encounter.getProvidersByRole(unknownRole).size());
assertEquals(provider, encounter.getProvidersByRole(unknownRole).iterator().next());
}
See complete example here: https://github.com/openmrs/openmrs-core/blob/master/api/src/test/java/org/openmrs/EncounterTest.java
Mocking in Spring Context
It is also possible to use @Mock in tests running in the Spring context. The @Mock annotation is recognized in all test classes extending BaseContextSensitiveTest. You can use @Mock to mock out certain services and @Spy to spy on services.
Mocking Context outside BaseContextSensitiveTest
Troubleshooting
If you see an error like this:
It means that you called initMocks using static method import in your test. The fix is simply to use MociktoAnnotations.initMocks(this) instead of initMocks(this) or remove initMocks(this) and rely on BaseContextSensitiveTest calling it.