Read or watch:
- Mocha documentation
- Chai
- Sinon
- Express
- Request
- How to Test NodeJS Apps using Mocha, Chai and SinonJS
- All of your code will be executed on Ubuntu
18.04
usingNode 12.x.x
- Allowed editors:
vi
,vim
,emacs
,Visual Studio Code
- All your files should end with a new line
- A
README.md
file, at the root of the folder of the project, is mandatory - Your code should use the
js
extension - When running every test with
npm run test *.test.js
, everything should pass correctly without any warning or error
0. Basic test with Mocha and Node assertion library
- Set up a scripts in your
package.json
to quickly runMocha
usingnpm test
- You have to use
assert
- Create a function named
calculateNumber
. It should accepts two arguments (number)a
andb
- The function should round
a
andb
and return thesum
of it
- Create a file
0-calcul.test.js
that contains test cases of this function - You can assume
a
andb
are alwaysnumber
- Tests should be around the
“rounded”
part
- For the sake of the example, this test suite is slightly extreme and probably not needed
- However, remember that your tests should not only verify what a function is supposed to do, but also the edge cases
- You have to use
assert
- You should be able to run the test suite using
npm test 0-calcul.test.js
- Every test should pass without any warning
> const calculateNumber = require("./0-calcul.js");
> calculateNumber(1, 3)
4
> calculateNumber(1, 3.7)
5
> calculateNumber(1.2, 3.7)
5
> calculateNumber(1.5, 3.7)
6
>
bob@dylan:~$ npm test 0-calcul.test.js
> [email protected] test /root
> ./node_modules/mocha/bin/mocha "0-calcul.test.js"
calculateNumber
✓ ...
✓ ...
✓ ...
...
130 passing (35ms)
bob@dylan:~$
- Upgrade the function you created in the previous task (0-calcul.js)
- Add a new argument named
type
at first argument of the function. type can beSUM
,SUBTRACT
, orDIVIDE
(string) - When
type
isSUM
, round the two numbers, and adda
andb
- When
type
isSUBTRACT
, round the two numbers, and subtractb
froma
- When
type
isDIVIDE
, round the two numbers, and dividea
withb
- if the rounded value ofb
is equal to0
, return the stringError
- Create a file
1-calcul.test.js
that contains test cases of this function - You can assume
a
andb
are alwaysnumber
- Usage of
describe
will help you to organize your test cases
- For the sake of the example, this test suite is slightly extreme and probably not needed
- However, remember that your tests should not only verify what a function is supposed to do, but also the edge cases
- You have to use
assert
- You should be able to run the test suite using
npm test 1-calcul.test.js
- Every test should pass without any warning
> const calculateNumber = require("./1-calcul.js");
> calculateNumber('SUM', 1.4, 4.5)
6
> calculateNumber('SUBTRACT', 1.4, 4.5)
-4
> calculateNumber('DIVIDE', 1.4, 4.5)
0.2
> calculateNumber('DIVIDE', 1.4, 0)
'Error'
2. Basic test using Chai assertion library
While using Node assert library is completely valid, a lot of developers prefer to have a behavior driven development style. This type being easier to read and therefore to maintain.
- Copy the file
1-calcul.js
in a new file2-calcul_chai.js
(same content, same behavior) - Copy the file
1-calcul.test.js
in a new file2-calcul_chai.test.js
- Rewrite the test suite, using
expect
fromChai
- Remember that test coverage is always difficult to maintain. Using an easier style for your tests will help you
- The easier your tests are to read and understand, the more other engineers will be able to fix them when they are modifying your code
- You should be able to run the test suite using
npm test 2-calcul_chai.test.js
- Every test should pass without any warning
Spies are a useful wrapper that will execute the wrapped function, and log useful information (e.g. was it called, with what arguments). Sinon is a library allowing you to create spies.
- Create a new file named utils.js
- Create a new module named
Utils
- Create a property named
calculateNumber
and paste your previous code in the function - Export the
Utils
module
Create a new file named 3-payment.js:
- Create a new function named
sendPaymentRequestToApi
. The function takes two argumenttotalAmount
, andtotalShipping
- The function calls the
Utils.calculateNumber
function with typeSUM
,totalAmount
asa
,totalShipping
asb
and display in the console the messageThe total is: <result of the sum>
- By using
sinon.spy
, make sure the math used forsendPaymentRequestToApi(100, 20)
is the same asUtils.calculateNumber('SUM', 100, 20)
(validate the usage of theUtils
function)
- You should be able to run the test suite using
npm test 3-payment.test.js
- Every test should pass without any warning
- You should use a
spy
to complete this exercise
- Remember to always restore a spy after using it in a test, it will prevent you from having weird behaviors
Spies
are really useful and allow you to focus only on what your code is doing and not the downstream APIs or functions- Remember that integration test is different from unit test. Your unit test should test your code, not the code of a different function
Stubs are similar to spies. Except that you can provide a different implementation of the function you are wrapping. Sinon can be used as well for stubs.
Create a new file 4-payment.js, and copy the code from 3-payment.js (same content, same behavior)
Create a new file 4-payment.test.js
, and copy the code from 3-payment.test.js
- Imagine that calling the function Utils.calculateNumber is actually calling an API or a very expensive method. You don’t necessarily want to do that on every test run
- Stub the function
Utils.calculateNumber
to always return the same number10
- Verify that the
stub
is being called withtype = SUM
,a = 100
, andb = 20
- Add a
spy
to verify thatconsole.log
is logging the correct messageThe total is: 10
- You should be able to run the test suite using
npm test 4-payment.test.js
- Every test should pass without any warning
- You should use a
stub
to complete this exercise - Do not forget to restore the
spy
and thestub
- Using
stubs
allows you to greatly speed up your test. When executing thousands of tests, saving a few seconds is important - Using
stubs
allows you to control specific edge case (e.g a function throwing an error or returning a specific result like a number or a timestamp)
Hooks are useful functions that can be called before execute one or all tests in a suite
Copy the code from 4-payment.js into a new file 5-payment.js: (same content/same behavior)
Inside the same describe, create 2 tests:
* The first test will call sendPaymentRequestToAPI
with 100
, and 20
:
* Verify that the console is logging the string The total is: 120
* Verify that the console is only called once
* The second test will call sendPaymentRequestToAPI
with 10
, and 10
:
* Verify that the console is logging the string The total is: 20
* Verify that the console is only called once
- You should be able to run the test suite using
npm test 5-payment.test.js
- Every test should pass without any warning
- You should use only one
spy
to complete this exercise - You should use a
beforeEach
and aafterEach
hooks to complete this exercise
Look into how to support async testing
, for example when waiting for the answer of an API or from a Promise
Create a new file 6-payment_token.js:
- Create a new function named
getPaymentTokenFromAPI
- The function will take an argument called
success
(boolean) - When
success
istrue
, it should return a resolved promise with the object{data: 'Successful response from the API' }
- Otherwise, the function is doing nothing.
- How to test the result of
getPaymentTokenFromAPI(true)
?
- You should be extremely careful when working with
async
testing. Without callingdone
properly, your test could be always passing even if what you are actually testing is never executed
- You should be able to run the test suite using
npm test 6-payment_token.test.js
- Every test should pass without any warning
- You should use the
done
callback to execute this test
When you have a long list of tests, and you can’t figure out why a test is breaking, avoid commenting out a test, or removing it. Skip it instead, and file a ticket to come back to it as soon as possible
You will be using this file, conveniently named 7-skip.test.js
const { expect } = require('chai');
describe('Testing numbers', () => {
it('1 is equal to 1', () => {
expect(1 === 1).to.be.true;
});
it('2 is equal to 2', () => {
expect(2 === 2).to.be.true;
});
it('1 is equal to 3', () => {
expect(1 === 3).to.be.true;
});
it('3 is equal to 3', () => {
expect(3 === 3).to.be.true;
});
it('4 is equal to 4', () => {
expect(4 === 4).to.be.true;
});
it('5 is equal to 5', () => {
expect(5 === 5).to.be.true;
});
it('6 is equal to 6', () => {
expect(6 === 6).to.be.true;
});
it('7 is equal to 7', () => {
expect(7 === 7).to.be.true;
});
});
- Make the test suite pass without fixing or removing the failing test
it
description must stay the same
- Skipping is also very helpful when you only want to execute the test in a particular case (specific environment, or when an API is not behaving correctly)
- You should be able to run the test suite using
npm test 7-skip.test.js
- Every test should pass without any warning
In a folder 8-api
located at the root of the project directory, copy this package.json
over.
{
"name": "8-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "./node_modules/mocha/bin/mocha"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.2.2",
"request": "^2.88.0",
"sinon": "^7.5.0"
}
}
- By using
express
, create an instance ofexpress
calledapp
- Listen to port
7865
and log API available onlocalhost
port7865
to the browser console when theexpress
server is started - For the route
GET /
, return the messageWelcome to the payment system
- Create one suite for the index page:
- Correct status code?
- Correct result?
- Other?
Terminal 1:
bob@dylan:~/8-api$ node api.js
API available on localhost port 7865
Terminal 2:
bob@dylan:~/8-api$ curl http://localhost:7865 ; echo ""
Welcome to the payment system
bob@dylan:~/8-api$
bob@dylan:~/8-api$ npm test api.test.js
> [email protected] test /root/8-api
> ./node_modules/mocha/bin/mocha "api.test.js"
Index page
✓ ...
✓ ...
...
23 passing (256ms)
bob@dylan:~/8-api$
- Since this is an integration test, you will need to have your node server running for the test to pass
- You can use the module
request
- You should be able to run the test suite using
npm test api.test.js
- Every test should pass without any warnings
In a folder 9-api, reusing the previous project in 8-api (package.json
, api.js
and api.test.js
)
- Add a new endpoint:
GET /cart/:id
:id
must be only anumber
(validation must be in the route definition)- When
access
, the endpoint should returnPayment methods for cart :id
- Add a new test suite for the
cart
page:- Correct status code when
:id
is a number? - Correct status code when
:id
is NOT a number (=>404
)? - etc.
- Correct status code when
Terminal 1:
bob@dylan:~$ node api.js
API available on localhost port 7865
Terminal 2:
bob@dylan:~$ curl http://localhost:7865/cart/12 ; echo ""
Payment methods for cart 12
bob@dylan:~$
bob@dylan:~$ curl http://localhost:7865/cart/hello -v
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 7865 (#0)
> GET /cart/hello HTTP/1.1
> Host: localhost:7865
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< X-Powered-By: Express
< Content-Security-Policy: default-src 'none'
< X-Content-Type-Options: nosniff
< Content-Type: text/html; charset=utf-8
< Content-Length: 149
< Date: Wed, 15 Jul 2020 08:33:44 GMT
< Connection: keep-alive
<
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /cart/hello</pre>
</body>
</html>
* Connection #0 to host localhost left intact
bob@dylan:~$
- You will need to add a small
regex
in your path to support the usecase
- You should be able to run the test suite using
npm test api.test.js
- Every test should pass without any warning
10. Deep equality & Post integration testing
In a folder 10-api
, reusing the previous project in 9-api (package.json
, api.js
and api.test.js
)
- Add an endpoint
GET /available_payments
that returns an object with the following structure:
{
payment_methods: {
credit_cards: true,
paypal: false
}
}
- Add an endpoint
POST /login
that returns the messageWelcome :username
where:username
is the value of the body variableuserName
.
- Add a test suite for the
/login
endpoint - Add a test suite for the
/available_payments
endpoint
Terminal 1:
bob@dylan:~$ node api.js
API available on localhost port 7865
Terminal 2:
bob@dylan:~$ curl http://localhost:7865/available_payments ; echo ""
{"payment_methods":{"credit_cards":true,"paypal":false}}
bob@dylan:~$
bob@dylan:~$ curl -XPOST http://localhost:7865/login -d '{ "userName": "Betty" }' -H 'Content-Type: application/json' ; echo ""
Welcome Betty
bob@dylan:~$
Tips:
- Look at deep equality to compare objects
- You should be able to run the test suite using
npm test api.test.js
- Every test should pass without any warning
- Your server should not display any error