Unit Testing

Unit Testing

When testing React and Angular OWA, we will create a mock webservice to test requests to the database. This is useful so that we can test our code without having to be connected to an online webservice. We see a mock webservice being created in an example test cases below. For React OWAs, in addition to testing database request, tests are usually made for proper rendering and state changes.

Choose your test type

Depending on what you selected when building your web app, you will either use React or Angular to test your OWA. Below are examples of each test type that you can use for your web app.

Your test file(s) should be stored as yourOWAfolder/test/exampleTest.js.

React (With Redux)

React OpenMRS Open Web Apps primarily use the framework Jest for its unit testing. Setting up Jest can be found here.

*All examples below are credited towards the Order Entry UI Module*

Testing Actions

When testing an action, the objective is to dispatch a function into a mock store and compare the action it dispatched to what action you expected. Your function will likely make request to a database and this can be tested by mocking a request. The example below uses moxios to mock axios request. The response of the request will determine what action is dispatched. 



Main code under test
const contextPath = window.location.href.split('/')[3]; export function fetchPatientRecord(patientUuid) { return dispatch => axiosInstance.get(`patient/${patientUuid}?v=custom:(patientId,uuid,patientIdentifier:(uuid,identifier),person:(gender,age,birthdate,birthdateEstimated,personName,preferredAddress),attributes:(value,attributeType:(name)))`) .then((response) => { dispatch({ type: SET_PATIENT, patient: response.data, }); }) .catch((error) => { if (error.response) { dispatch({ type: SET_PATIENT_FAILED, }); window.location.href = `/${contextPath}`; } }); } export function fetchPatientNote(patientUuid) { return dispatch => axiosInstance.get(`obs?concept=162169AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&patient=${patientUuid}&v=full`) .then((response) => { dispatch({ type: SET_NOTE, note: response.data.results, }); }); }



Action test
import { fetchPatientRecord, fetchPatientNote, } from '../../app/js/actions/patient'; import { SET_PATIENT, SET_NOTE, SET_PATIENT_FAILED } from '../../app/js/actions/actionTypes'; window.location = locationMock; const uuid = '6cesf-4hijk-mkls'; // The describe function is for grouping related specs, typically each test file has one at the top level describe('Patient actions', () => { beforeEach(() => moxios.install()); afterEach(() => moxios.uninstall()); // The it function represent a spec or a test case. it('fetch patient records', async (done) => { const { defaultPatient } = mockData; let request = moxios.requests.mostRecent(); moxios.stubRequest(`${apiBaseUrl}/patient/${uuid}?v=custom:(patientId,uuid,patientIdentifier:(uuid,identifier),person (gender,age,birthdate,birthdateEstimated,personName,preferredAddress),attributes:(value,attributeType:(name)))`, { status: 200, response: defaultPatient }); const expectedActions = [{ type: SET_PATIENT, patient: defaultPatient }]; const store = mockStore({}); await store.dispatch(fetchPatientRecord(uuid)) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); done(); }); it('fetch patient records failure case', async (done) => { let request = moxios.requests.mostRecent(); moxios.stubRequest(`${apiBaseUrl}/patient/${uuid}?v=custom:(patientId,uuid,patientIdentifier:(uuid,identifier),person:(gender,age,birthdate,birthdateEstimated,personName,preferredAddress),attributes:(value,attributeType:(name)))`, { status: 400, response: { message: "No record found" } }); const expectedActions = [{ type: SET_PATIENT_FAILED, }]; const store = mockStore({}); await store.dispatch(fetchPatientRecord(uuid)); expect(store.getActions()).toEqual(expectedActions); done(); }); it('fetch patient\'s note', async (done) => { const { defaultNote } = mockData; moxios.stubRequest(`${apiBaseUrl}/obs?concept=162169AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&patient=${uuid}&v=full`, { status: 200, response: defaultNote }); const expectedActions = [{ type: SET_NOTE, note: defaultNote.results }]; const store = mockStore({}); await store.dispatch(fetchPatientNote(uuid)) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); done(); }); });



Testing Components

When testing a component, the objective is to check if the component gets mounted correctly and handles events correctly.



Main code under test
import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { ToolTip } from '@openmrs/react-components'; import '../../../css/grid.scss'; const formatPanelName = (panelName) => { const name = panelName; return name.replace(/panel/i, '').trim(); }; const formatToolTipData = setMembers => setMembers.map(test => test.display); const LabPanelFieldSet = (props) => { const { selectedPanelIds, handleTestSelection, panels, labCategoryName, } = props; return ( <fieldset className="fieldset"> <legend>Panels</legend> <div className="panel-box"> {panels.length ? ( panels.map(panel => ( <button id="panel-button" className={classNames('lab-tests-btn tooltip', { active: selectedPanelIds.includes(panel.uuid), })} type="button" key={`${panel.uuid}`} onClick={() => handleTestSelection(panel, 'panel')}> {formatPanelName(panel.display.toLowerCase())} <ToolTip toolTipHeader="Tests included in this panel:" toolTipBody={formatToolTipData(panel.setMembers)} /> </button> )) ) : ( <p>{labCategoryName} has no panels</p> )} </div> </fieldset> ); }; LabPanelFieldSet.defaultProps = { selectedPanelIds: [], }; LabPanelFieldSet.propTypes = { handleTestSelection: PropTypes.func.isRequired, labCategoryName: PropTypes.string.isRequired, panels: PropTypes.array.isRequired, selectedPanelIds: PropTypes.array, }; export default LabPanelFieldSet;



Component test



Testing Reducers

When testing a component, the objective is to check if the reducers change the application state as intended given a specific action. Your function will most likely compare two states.  This example below creates an action and initial state then passes them to the reducer.



Main code under test



Reducer test



Setup

OpenMRS React OWA typically have a setup file for test to handle import that will be used across all tests. This is to help abide by DRY and avoid repetitive code. An example of this is shown below:



Order Entry UI's test setup file



In order to properly run tests and apply the setup file, Jest needs to be configured. An example configuration file is shown below: 



Order Entry UI's jest.config File



Useful Links

ReactJS Testing

Jest

Jest Documentation

Order Entry UI's Github Repo

Angular 

Angular OpenMRS Open Web Apps also primarily use the framework Jasmine and the test runner Karma for its unit testing.  To set up our mock backend, we use $httpbackend service. $httpbackend has the method whenGet that will create a new GET request.  There needs to be enough whenGet calls that will receive all of the information that your controller expects. $rootScope needs to be created before each spec to observe model changes. $componentController creates an instance of a controller. Make sure you have the name of controller that you are testing in the parameters.



Useful Links

Angular: Unit Testing Jasmine, Karma (step by step)

Jasmine

Karma

Angular Testing Guide

$httpBackend

$rootScope

$componentController

How to Run Unit Tests

In the root of your project directory, run the command:

Run Tests Only



Build Project and Run Tests

Troubleshooting

Angular Owa Fails to Run Tests and Build after Generating OWA

This is a potential fix for this error message:

1) Open package.json which is in your OWA's root folder, and under dependencies, remove the "^" before the version number of each following Angular dependency: angularangular-route, and angular-mocks.

2) In the root of your project directory, run the command:

Install Correct Angular Dependencies

3) In home.js of your project remove this code:

4) Re-run test and/or build