-
Notifications
You must be signed in to change notification settings - Fork 4
Testing
We use automated tests to ensure quality code and speed up the discovery of bugs and regressions and to ensure that we have delivered user stories.
Before creating a pull request, you should ensure that all the tests pass locally. Additionally, if there is no Continuous Integration (CI) service running then code reviewers should also check that the tests pass for the branch they are reviewing
To run the unit tests, use the following command
yarn test
This will run all the tests and output a code coverage report. You should ensure that code coverage does not regress if no code coverage tool is integrated as part of the pull request process.
To run the e2e tests, use the following command
yarn e2e
This will build a production version of the code, serve it and run the e2e tests against said served site.
If you would like to see the browser interactions yourself, run the command:
yarn e2e:interactive
Which will bring up the Cypress
UI which you can use to select which tests to run and see the Cypress
test runner interacting with the browser.
If you are creating a new feature then you should ensure that you add the relevant tests for said code. Most tests written will be unit tests, with e2e tests written instead for ensuring user stories are achieved and to test code that is hard to test in unit tests.
In general, the testing and assertion libraries make it relatively easy to read tests as the assertions are meant to emulate human reasoning e.g. expect(2 + 2).toEqual(4)
and so looking at other test files can be useful to figure out how to do something if what you're testing has similarities to other parts of the app.
When you create a new component myComponent.tsx
, a corresponding myComponent.test.tsx
file should be created in the same folder. Whilst developing tests, then running the tests in watch mode is useful as every time you save either your test file or the files you are testing the tests will rerun. To do so, run the following command:
yarn test:watch
You can select options using w
so do things like only run tests on files that have changed, or only run failed tests, or run specific files or tests based on regex matching filenames or testnames. Additionally, you can choose to run a single test file not in watch mode by passing a subset of the name of the test file to the test command e.g.
yarn test myComponentTests
You should then write tests using the Jest
test framework and Enzyme
, if you are new to Jest & Enzyme then the Flavio Copes Jest tutorial and Scotch Enzyme and Jest tutorial are a good start, but in general the process goes as follows:
-
Use
describe
to group relevant tests together e.g.describe('My Component', () => { // your tests here });
-
Use
beforeEach
to set up data before each test runs- e.g. Set up
shallow
for shallow rendering andmount
for full DOM renderinglet shallow; let mount; beforeEach(() => { shallow = createShallow({}); mount = createMount(); });
- e.g. Set up mock redux store
beforeEach(() => { mockStore = configureStore(); state = JSON.parse(JSON.stringify({ daaas: initialState })); });
- e.g. Set up
-
write a singular test case with a descriptive name
it('should render correctly', () => { // your test code here });
-
Shallow render or mount your component depending on what you test does. Mount actually renders the DOM code - so DOM interactions like clicks, or rendering subcomponents need
mount
, otherwise useshallow
e.g. if your component needs the store:it('should render correctly', () => { const wrapper = shallow(<MyComponent store={mockStore(state)} />); });
or
it('should render correctly', () => { const wrapper = mount( <Provider store={mockStore(state)}> <MyComponent /> </Provider> ); });
-
Assert something about your component or interact with it and assert
e.g. Snapshot test - tests that your component looks the same - this should be done for most components
it('should render correctly', () => { const wrapper = shallow(<MyComponent store={mockStore(state)} />); expect(wrapper.dive().dive()).toMatchSnapshot(); });
e.g. clicking a button sends an action
it('sends action when button clicked', () => { const testStore = mockStore(state); const wrapper = mount( <Provider store={testStore}> <MainAppBarComponent /> </Provider> ); wrapper .find('button') .first() .simulate('click'); expect(testStore.getActions().length).toEqual(1); expect(testStore.getActions()[0]).toEqual(action()); });
A summary of the code coverage is available after running unit tests. For details information regarding the uncovered lines look into the code coverage report that is generated at the location: [package name]/coverage/lcov-report/index.html
When snapshots fail, that means that the underlying DOM structure of your component has changed. You need to confirm that this change was intended, e.g. you added a new component or property or something, or if it is something you didn't intend then you need to figure out why it changed and fix the issue (if it is an issue - sometimes the underlying structure changed due to changes in Material UI or another library and you can just update snapshots).
To update all failing snapshots, run:
yarn test -u
and commit the changes.
To interactively change snapshots i.e go through one by one and accept and reject changes, then enter test watch mode:
yarn test:watch
Then bring up the watch menu by pressing w
to see all your options and then press i
to select the interactive snapshot update mode.
When a new user story thought to have been completed, then before it can be marked as complete an e2e test should be created. Also, if there is a part of a component that cannot be tested by an unit test, then writing an e2e test for that test case is also advised.
Whilst writing e2e tests, it is recommended to use the interactive mode of Cypress as this means you won't have to rebuild your server every time and can use the Cypress test tools and browser developer tools to help you (e.g. find css selectors). As a reminder, the interactive test command is shown below:
yarn e2e:interactive
There are a couple of things that you can do if you ever get a Cypress failed to start
error message while running the above command:
- Make sure that the cypress versions are correct in the
package.json
andyarn.lock
files. If they are then telete thenode_modules
directory and runyarn install
. - If the above does not fix the issue then clear the cypress cache by running
yarn cypress cache clear
. Note that you may also need to runyarn cypress install
.
You should then write tests using the Cypress
test framework, if you are new to Cypress then the Cypress docs are a good start, but in general the process goes as follows:
- Create a new test spec in
cypress/integration
with the name formattest_name.spec.ts
- Use
describe
to group relevant tests together e.g.describe('Login', () => { // your tests here });
- If needed, use
beforeEach
to set up data before each test runs. See the unit test docs above for an example - write a singular test case with a descriptive name
it('should login given correct credentials', () => { // your test code here });
- Visit a page, and interact with it and assert things based on said interactions
it('should login given correct credentials', () => { cy.visit('/login'); cy.get('button[aria-label="Open navigation menu"]').should( 'not.be.visible' ); cy.contains('Username*') .parent() .find('input') .type('username'); cy.contains('Password*') .parent() .find('input') .type('password'); cy.contains('Username*') .parent() .parent() .contains('button', 'Sign in') .click(); cy.url().should('eq', 'http://127.0.0.1:3000/'); cy.window().then( window => expect(window.localStorage.getItem('daaas:token')).not.be.null ); cy.get('button[aria-label="Open navigation menu"]').should('be.visible'); });
-
Architecture
-
Dev environment
-
Developing a plugin
-
Deployment
- Deploying SciGateway
- SciGateway Settings
- Deploying plugins
-
Releasing
-
Plugins
-
Continuous Integration
-
UX
-
Feedback