Skip to content

Testing

Mason Park edited this page Mar 2, 2023 · 4 revisions

Testing

Table of Contents

Preface

The back end uses Jest as the testing framework of choice and nock to mock requests.

The front end uses Jest as the testing framework of choice and React Testing Library for APIs for working with React components in tests.

Types of Tests

Unit tests

Integration tests

  • We try to mostly write integration tests, with unit tests when necessary
  • On the front-end, in addition to normal integration tests, we also have a type of integration test called snapshot tests. Read more here. To update snapshot tests that fail due to an expected added feature or update, simply execute npm test -- -u

End to end tests

Running the Tests

Run all tests

Make sure you are in the correct working directory.

$ pwd
~/waterthetrees/wtt_server

Then run the command:

npm test

Additional Configurations

You can add additional arguments to npm test by suffixing it with --, followed by additional command line arguments.

For all configuration options read Jest CLI Options.

Run a Subset of Tests

Run tests that match a pattern or file name.

npm test -- myTest.test.js

Testing Philosophy

Use the AAA (Arrange, Act, Assert) pattern

A test consists of three functional sections.

  • Arrange – Set up input variables and preconditions.

  • Act – Perform the behavior under test with the arranged parameters. (typically 1 line of code)

  • Assert – Verify the result. (typically 1 line of code)

Test for the Outcomes

Test the outcomes of triggering an action (e.g. API call). That is, we care about what happens at the end of the action, not how it happens.

Some aspects to check for are:

  • Response - Check the response data correctness, schema, and HTTP status.

  • A new state - If the behavior under test modifies some data, check that the data is updated correctly.

  • Observability - Some things must be monitored, like errors or remarkable business events. When a transaction fails, not only do we expect the right response but also correct error handling and proper logging/metrics.

Start By Testing the Longest Happy Path

According to Wikipedia, a happy path is a default scenario featuring no exceptional or error conditions. For example, the happy path for a function validating credit card numbers would be where none of the validation rules raise an error, thus letting execution continue successfully to the end, generating a positive response.

In happy path testing, we test the functionality in the way that it was designed to work. Don't try to break it. If all the correct actions were taken, what should the response be?

Only after we test the happy path should we test for corner cases.

Structure Tests by Routes and Stories

Group tests by routes and within each route use the When... Then... naming pattern to tell the expected behavior of each path. Name all tests clearly enough so that a newcomer could understand the intended outcomes of an action solely through reading the test's description.

Each test should work in isolation and not depend on, nor be affected by previous tests.

Debugging Tips

No match for request

If you set up a nock object

nock('http:https://localhost:3002/api/trees').get('9').reply(200);

And you get Nock: No match for request after running the test

Nock: No match for request {
  "method": "GET",
  "url": "http:https://localhost:3002/api/trees/9/",
  "headers": {
    "accept": "application/json, text/plain, */*",
    "user-agent": "axios/0.21.1"
  }
}

You must have a matching request to the URL specified in the nock object.

const res = await axios.get('http:https://localhost:3002/api/trees/9');

Now we should have a matching call, but the error persists. Double check that both requests resolve to the same URL. For this example, we must change the argument in .get() from the nock object.

 nock('http:https://localhost:3002/api/trees').get('9').reply(200)
 nock('http:https://localhost:3002/api/trees').get('/9').reply(200);
Check the address

Although address that axios is called on should contain the port that the server is on, the nock object should be called without the port.

const response = await axios.get('/api/trees/9');
nock('http:https://localhost/api').get('/tree/9');
Multiple requests to the same nock object

A nock object only be used once by default.

nock('http:https://localhost:3002/api/trees').get('/9').reply(200);

const res1 = await axios.get('http:https://localhost:3002/api/trees/9');
const res2 = await axios.get('http:https://localhost:3002/api/trees/9');

Although res1 and res2 have the same exact API call, the response from res1 will match the reply block in the nock object while the response from res2 while correspond to a non-mocked API call.

To use the same nock object for multiple API calls, consider using .persist().

Useful Resources For Further Reading