rosemelissa / mark-nodejs-proj--endpoint-testing

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Endpoint testing

Creative Commons License

This is part of Academy's technical curriculum for The Mark. All parts of that curriculum, including this project, are licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

We're now going to run your first server and play around with a few existing endpoints that it has.

Learning Outcomes

  • Compare the brittleness of different tests
  • Write endpoint tests for an express server with supertest
  • Use Jest's documentation on matchers
  • Use Jest's toMatch matcher with a regular expression
  • Load an environment variable from a .env file into Node

Exercise 1: Installing, running and testing

Success criterion: you can view evidence your server is running at localhost:4000 and run its tests (which should all pass)

Clone/fork the repo so that you have it locally, then view the scripts available in package.json. This should give you an idea of what you can run to get the server started, run its tests, and make queries via Postman.

(The server doesn't need to be running for you to run tests - supertest is a testing library that lets us test HTTP requests.)

Exercise 2: Environment variables

🎯 Success criterion: you can view the effects of loading in your environment variables and articulate why a .env should not be tracked in git

In index.ts you'll see the following code:

// load .env file contents into process.env
dotenv.config();

// use either specified port number in .env or 4000
const PORT = process.env.PORT_NUMBER ?? 4000;

(The nullish coalescing operator ?? happens to be used here, but that's an unimportant detail.)

.env is a file where it is common to store 'environment variables', which you can read about here.

One of the important takeaways is adding your .env to .gitignore (which has been done for you in this example).

This is because .env files can contain secrets which we don't want to risk exposing to the world.

We've included an example .env file as .env.example - rename this to simply .env and restart your server (needed to load the new environment variables).

You should see the two environment variables take effect in two ways:

  1. the specified port number will be used
  2. the specified message will be logged out on server start

Because we don't typically track .env in source control, it is common to include a sample file (such as .env.example, or sample.env) to show somebody how their local (untracked) .env file should look.

Exercise 3: Reading, understanding and documenting

🎯 Success criterion: a document which outlines how you think this Express server works. You don't have to achieve a theory which explains 100%, but you should strive to explain as much as possible.

(N.B.: The correctness of your theory is much less important than the process of forming this document. Forming a prediction, and then discovering it was wrong, is an effective way to learn!)

  1. Take some time to read and digest the code
  2. Google things that you don't understand
  3. Experiment with changing things
  4. Produce a narrative document

A note on the tests

The tests have been written using a few different Jest matchers.

We'll look at three different strategies for testing the content of response.body.speech.text.

GET /

By using .toStrictEqual on the response body, our test for GET / is the most britle. It's specific, prescriptive and rigid - it's easy for a non-meaningful change to break it.

A small punctuation change (e.g. "Hmm, I reckon we should have an exclamation mark rather than a full stop after 'ENDPOINT ADVENTURE'") requires a change in two places: the test file and the server file.

Additionally, the assertion (which checks the whole of response.body) will fail if we make a change to a different part of the body (e.g. response.body.options).

GET /quest/accept

In this test, we're using the below assertion:

expect(typeof response.body.speech.text).toBe("string");

we are only asserting that response.body.speech.text is a string - but it could be any string, e.g. the empty string '' or a total non-sequitur like "You have no authority here, Jackie Weaver!"

This is far less brittle, but it also may be insufficiently stringent as a test.

GET /quest/decline

In this test, we're using the below assertions:

expect(response.body.speech.text).toMatch("FOOL");
expect(response.body.speech.text).toMatch(/mistake/i);

The first of these checks that "FOOL" appears somewhere in the string (but doesn't care where).

The second is using a regular expression, looking for the pattern mistake (enclosed within / to declare a regular expression literal), with the i flag added at the end to indicate a case-insensitive search: so "MISTAKE" and "miSTakE" both match.

(The equivalent _RegExp_ for "FOOL" in our first assertion would have been /FOOL/, with no case insensitive flag - since the string "FOOL" is not a case insensitive matcher.)

These assertions are less brittle than testing for an exact string and also more stringent and informative than simply checking it is a string. They communicate something about intent by giving clues as to what sort of message should be represented in the string, without being prescriptive down to punctuation and phrases.

Exercise 4: Writing and satisfying endpoint tests

🎯 Success criterion: your server passes the test for GET /quest/start/impossible, you have a passing test written for GET /help, and you have used TDD with GET /quest/start/easy and GET /quest/start/hard

Pass the test for /quest/start/impossible

There is currently a skipped test for GET /quest/start/impossible. Un-skip it, and write the route handler to make it pass.

Write the test for /help

There is currently a handler written for GET /help, but no associated test. Write a sensible test (making a judgement on stringency and brittleness) for it.

TDD for /quest/start/easy and /quest/start/hard

When the user accepts the quest (GET /quest/accept), they have three options presented to them. Currently, just one of these has a defined test and route handler.

Write the code for the remaining two routes, using TDD - starting with the tests and then progressing to the route handlers.

Exercise 5: Further TDD endpoints

🎯 Success criterion: you have added at least three further endpoints using TDD with supertest

This takes us only to the start of the quest - there's no actual questing happening yet!

Let's remedy that - add at least three further endpoints using TDD.

Exercise 6: Commentary and reflection

🎯 Success criterion: documented reflections.

About


Languages

Language:TypeScript 99.4%Language:JavaScript 0.6%