Page tree
Skip to end of metadata
Go to start of metadata

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

TRUNK-3586 - Getting issue details... STATUS  introduced ContextMockHelper which is deprecated, using powermock for mocking Context should also help.

Troubleshooting

If you see an error like this:

error: method initMocks in class BaseContextSensitiveTest cannot be applied to given types;
[ERROR]  actual and formal argument lists differ in length

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.

  • No labels