Unit Tests
The dashboard is configured to run unit tests with Jest in combination of vue-test-utils, for Vue scoped cases.
Requirements to accept tests:
- JS and TS formats
- Suffix with
.test
or.spec
- Contained in any directory
__tests__
Adopted commands:
yarn test
, run and watch every testyarn test:ci
, script used for CI, which outputs a coverage report to/coverage
folder
Example tests can be found in /components/__tests__
. For more information about testing vue components, see the vue test utils and jest docs.
VSCode debugging tools
It is possible to use debugging tools within Jest via VSCode. To do so, open the debugger panel (Ctrl/Cmd+Shift+D) and select the Debug Jest Tests
option from the dropdown. This will start a debug session with the Jest tests, allowing you to set breakpoint, inspect code and visualize variables on the panel itself. As usual it's possible to execute the tests by F5
after selecting the right option.
Style guide
On top of the recommendation provided by the Vue documentation, it is also encouraged to follow these patterns to create readable and aimed tests.
Jest global configuration
Some of the global configuration for Jest can be found in the jest.setup.js
file, mainly to avoid repetitions. This will include:
- Global Vue mounted
- Components
- Modules
- Directives
- Getters (e.g., i18n/t)
- Global hooks, e.g.
afterEach()
with mocks resets
Describe and test/it statement
To clearly state the scope of the test, it's convenient to define in the first describe
always define with a noun the name of the function, method, or component being tested. Multiple assertions may be grouped together under a common statement in describe
block, as it helps to avoid repetition and ensure a set of tests to be included. Each test
/it
block should then start with a verb related to what is the expectation.
describe('myfunction', () => {
describe('given the same parameter', () => {
it('should return the same result', () => {
// Test code
});
it('should return something else for a second parameter', () => {
// Test code
});
});
});
For further information, consult the Jest API documentation.
Simple tests
Test with the highest readability and reliability should avoid logic, as this will increase line of code and have to be tested as well. Static data is then preferred over computation and should be declared always within the describe or test or as close as possible.
Don't:
test('define if is required to use this in our component from the response', () => {
const myData = externalFunction(externalData);
for(data in myData) {
data.key = 'something else'
}
expect(isRequired(myData)).toBe(true);
});
Do:
describe('FX: isRequired', () => {
test('should return true', () => {
const myData = { key: 'required case' };
const result = isRequired(myData);
expect(result).toBe(true);
});
});
AAA pattern
Adoption of AAA format (arrange, act, assert) for tests.
- Arrange is where you prepare test, e.g. set properties to a component or declare variables
- Act is when an event or function is triggered
- Assertion correspond to the expectation of the test
Don't:
describe('FX: isRequired', () => {
test('should return true', () => {
let myData = { key: 'required case' };
expect(myData).toBeTruthy();
myData.key = 'something else';
const result = isRequired(myData);
expect(result).toBe(true);
myData['key2'] = 'another key/value';
expect(result).toBe(false);
});
});
Do:
describe('FX: isRequired', () => {
test('should return true', () => {
const myData = { key: 'required case' };
const result = isRequired(myData);
expect(result).toBe(true);
});
test('should return false if malformed data', () => {
const myData = {
key: 'required case',
key2: 'another key/value'
};
const result = isRequired(myData);
expect(result).toBe(false);
});
});
Behaviors over implementations
As also defined in the Vue documentation for component and composable testing, it is recommended to test rendered elements over internal API of the component.
Following an input example as in the documentation.
Don't:
const wrapper = mount(YourComponent);
const inputWrapper = wrapper.find(`[data-testid=your-component]`;
inputWrapper.setValue(1);
expect(wrapper.emitted('input')[0][0]).toBe(1);
Do:
const wrapper = mount(YourComponent);
const inputWrapper = wrapper.find(`[data-testid=your-component]`;
inputWrapper.setValue(1);
expect(wrapper.text()).toContain('1')
Parameterization
When multiple cases are required to be tested for the same component, it is recommended to avoid multiple actions and assertions or even worse logic, but rather rely on Jest functions to parametrize the test.
Don't:
describe('FX: isRequired', () => {
test('should return true', () => {
let myData = { key: 'required case' };
expect(myData).toBeTruthy();
myData.key = 'something else';
expect(result).toBe(true);
myData.key = 'another value';
expect(result).toBe(true);
});
});
Do:
describe('FX: isRequired', () => {
test.each([
'required case',
'something else',
'another value',
])('should return true', (key) => {
const myData = { key };
const result = isRequired(myData);
expect(result).toBe(true);
});
});
Coverage
Both unit and E2E tests generate coverage respectively with Jest and NYC. These values are generated on both PR and push to master
and release
after merging. The service used to display the values is Codecov and can be found here.
Special attention goes to the E2E as the code is instrumented with Babel and the configuration is set within Nuxt.js.