Skip to content

Latest commit

 

History

History
116 lines (89 loc) · 3.89 KB

testing.md

File metadata and controls

116 lines (89 loc) · 3.89 KB

Testing

Unit testing

  • Better specs (although created for RSpec and Ruby) contains many good general principles
  • Rubocop's guide on test naming (although created for RSpec and Ruby) contains many good general principles
  • Test coverage aim: 100%. This doesn't mean that the build should fail if it's 99, but specifying a lower aim makes no sense as the goal is to have everything covered
  • Tests are written to test, not to increase coverage
  • Their scope is very limited (to not bleed into other code, both functionality and coverage wise) and it is clear what they test

Naming

  • Name of testfile is the same as name of tested file. Additional suffex might be added as per framework conventions. E.g.
    • JS with Jest: getProxyURL.js => getProxyURL.test.js
    • Ruby with RSpec: get_proxy_url.js => get_proxy_url_spec.js
  • Test are described in 3 parts: describe, context, it
    • describe simply states the name of the component / function
    • context elaborates on additional state / setup
      • only if needed
      • always starts with when, with or without
      • forms a sentence with proper grammar when composed with it block descriptions
      • uses context function (or describe if the former is not available)
      • does not describe action, only state (e.g. correct: when button is inactive. incorrect: when button is clicked)
      • usually has a counter-example with the opposite state
    • it states what the tested piece of logic (referenced by describe) does in the given context

Practical example:

`describe('getProxyURL', () => {
  describe('when CORS_PROXY_ENABLED env var is true', () => {
    it('returns the given URL with a CORS proxy', () => {
      # test
    });
  });
});

This read nicely as: describe getProxyURL: when CORS_PROXY_ENABLED env var is true, it returns the given URL with a CORS proxy

Always double check whether the test actually does what the description says. E.g.

describe ('delete button, () => {
  it ('removes the item`, () => {
    originalLength = items.length;
    wrapper.find('deleteButton').simulate('click');
    expect(items).toHaveLength(originalLength - 1);
  });
});

This test desciption is not correct. It checks that when the delete button is clicked on an item, it removes an item, not necessarily the item. Tests descriptions are there so that we don't have to look at the tests themselves, but that's only the case if they're accurate. It might be fine to have a test like the above, but the description should read e.g. it descreases the number of items by 1.

Independence of tests

Tests are always independent of each other.

Best practices include

  • using per test case context setups (i.e. before each, not before all)
  • resetting common storages between tests (e.g. DB, locale storage)
  • resetting mocks between tests

It is enforced by running tests isolated or in a randomized order.

Mocking / stubbing / spying

Should be used

  • to skip unreliable / slow parts of the code (e.g. network requests)

Could be used

  • to avoid testing code written for the project which is not the subject of the test
  • to avoid indeterminate functionality

GOOD

it "creates user with a unique key" do
  key = 'dummy_unique'
  allow(UniqueGenrator).to receive(:hex).and_return(key)
  
  expect(User.create.key).to eq(key)
end

Should not be used

  • unless there's a reason to use them (a.k.a. this is not the default approach to testing)
  • to skip 3rd party code (e.g. DB, framework)
  • as a replacement for verifying return values

GOOD

get :index
expect(response).to be_successful

BAD

expect(response).to receive(:send_status).with(200)
get :index
  • as a replacement for verifying side effects

GOOD

deleteUser(id)
expect(User.find(id)).not_to be

BAD

expect(User).to receive(:delete).with(id)
deleteUser(id)