Testing Code
We test the Chemotion ELN using three different kinds of tests.
1. JavaScript unit tests (mocha)
npm test
2. Ruby unit tests (RSpec)
RAILS_ENV=test bundle exec rake db:test:prepare && bundle exec rspec --exclude-pattern spec/features/**/*_spec.rb
3. Acceptance (feature) tests (Capybara)
currently the system testing is in transition to the Cypress framework. Once finished the documentation will be added
RAILS_ENV=test bundle exec rake db:test:prepare && bundle exec rake assets:precompile && bundle exec rspec spec/features
General rules of thumb
- All public methods should be tested, every statement, conditional branch should be covered.
- Tests should be easy to read and understand. Try to use a maximum of 3 describe/context levels:
- Describe: What is the class/method I want to test?
- Context: Under what circumstances is the method tested?
- It: What do I expect?
- Verifying UI state should not be the focus of unit testing (with the exception of snapshot testing in JavaScript).
- Useful resources
Javascript Unit tests
- Test files should be placed in spec/javascript/..., mirroring the location of the source file (i.e., the file under test).
- Test files should have the name of the file under test but end with .spec.js.
- For simple plain JavaScript objects, test every "public" method. Without access modifiers, look what methods are called by other components.
Testing React components
- Use the Enzyme library to create React components:
wrapper = shallow(<CLASS_OF_COMPONENT/>);
- React components can be used with their JavaScript-state or their HTML representation:
wrapper.instance()
andwrapper.html()
- Example: ResearchPlanDetailsFieldImage.spec.js
When testing a component which includes a store you need to explicitly import he store in the test file. The import must be before the import statement of the component to test. The linter recognizes that the store dependency is not used and tries to eliminate it. You can prevent this by manually disabling the linter rule for these lines. Example: ResearchPlanDetails.spec.js
Stubbing/Mocking
- Heavy dependencies like REST calls can be stubbed/mocked in a test
- We use the library sinon
Sinon.stub(CLASS_TO_MOCK, "METHOD_NAME").callsFake(LAMBDA as replacement of original method);
- Example: ResearchPlanDetailsFieldImage.spec.js
Creating models with factories
- To create model objects, we use the library factory-bot
const researchPlan = await ResearchPlanFactory.build('empty');
- By using a pseudoRandomGenerator in the factory for ids and checksums we can create reproducible objects
- One can reset the generator by adding a build option:
const researchPlan = await ResearchPlanFactory.build('empty', {}, { reset: true });
Creating a model is an asynchronous action, so the test must also be asynchronous: it('no img tag should be rendered', async function (){ }
Snapshot testing
Currently no snapshot testing is applied due to lack of capacity. In addition, ui testing will be covered by the system tests in the future.
- When testing a React component, we want to verify the state of the component. An easy way to achieve this would be to check if its HTML state is as expected.
- By using the snapshot library expect-mocha-snapshot, a snapshot of the HTML state is generated at the first test run. After that, the state in the test is checked against the saved state from the first run.
- Snapshots are saved in a folder snapshots at the same level of the test
- Snapshots are not only restricted to HTML but can also be used for JSON or JavaScript-objects
- Example: ResearchPlanDetailsFieldImage.spec.js
The expect
extension by mocha refers to the current test-object by this
. Using an lambda expression at the it definition, mocha have no access to it. Using an explicit
function definition solves this problem.
Ruby Unit tests
Common infos
- Tests should reflect the structure of the source package. That means having the exact same location and name ending with
_spec.rb
. - Example test
Creating database models with factory bot
- Models can be created using the library factory bot. In factories, desired states of the models are predefined and can then be created using
create
(creates a DB entry) orbuild
(creates a model without a DB entry).
Creating an attachment with factorybot must be handled carefully because create
also triggers the create_derivative
method and could remove a fixture file.
By using build
the create_derivative
method is not executed, but one has no object in the database and some properties are not initialized.
Testing methods without classes
- can be done by adding type :helpers
Rspec.describe GenericHelpers, type: :helper do
.
Example generic_helpers_spec.rb
Stubbing/Mocking
- REST calls
stub_request(REST_METHOD_AS_SYMBOL, 'REST_ADDRESS'
.with(REQUEST_HEADER_INFOS OR/AND PARAMETER)
.to_return(RETURN BODY OR/AND HEADER)
Example: spec_helper.rb
Many REST requests that request INCHI keys are already defined in spec_helper.rb
- Methods of classes/objects
allow_any_instance_of(CLASS_NAME).to receive(METHOD_NAME_AS_SYMBOL).and_return(RESULT)
allow_any_instance_of(CLASS_NAME).to receive(METHOD_NAME_AS_SYMBOL).and_raise(AN ERROR)
- for more infos: rspec-mocks
API - testing
- Tests for the api have to be in the subfolder spec/api/... to work properly
Acceptance tests
When you have freshly installed Chemotion ELN, make sure to create a welcome-message.md
file in the public directory inside Chemotion ELN before running acceptance tests. You can create it from the example file:
Coverage
Infos about code coverage: Code Coverage
JavaScript unit tests
- Using
npm run coverage
creates a coverage report in HTML written in .coverage. The method of covering is condition coverage. - in package.json one can exclude directories from coverage
- Coverage is currently not included in CI
Rspec unit tests
By running the rspec unit tests, the coverage is currently calculated automatically. The results can be found in the folder .coverage