Locally unit-testing source code for a Node.js Hello World webapp
09 Mar 2023
Let’s add some unit tests to the tiny webserver we built and ran this series’s kickoff article.
See the sample codebase on GitHub.
Prerequisites
- Please work your way through my entire exercise “Source code that builds locally into a Node.js Hello World webapp” before beginning this exercise so that you’re familiar with “building” and “running” a webserver (although in this case, we won’t do either of those things – instead we’ll be “testing” it) from source code.
- As mentioned there, you need to have Node.js installed and NPM installed onto your local computer.
- Download a copy of my sample codebase for this article onto your local computer, being sure to give it a completely different folder from the one you used for the previous exercise.
- Using a command line interface – ensuring first that its prompt indicates that your commands will be running within the context of the folder into which you downloaded a copy of my sample codebase for this article – you must run the following command:
npm install
The npm install
command will take about a minute to execute.
It will add a new subfolder and a new file to the folder on your computer containing a copy of my sample codebase:
- A
/node_modules/
folder (important, and note that its contents will be different than in the last exercise). - A
/package-lock.json
file (unnecessary for this exercise but not hurting anything).
Running tests that pass
Open up a command line interface.
Ensure that its prompt indicates that your commands will be running within the context of the folder into which you downloaded a copy of my sample codebase.
Run the following command:
npm run test
The output you will see will say something like:
> test
> cross-env NODE_ENV=test jest --testTimeout=10000 --detectOpenHandles
PASS src/__tests__/my-first-test.js
Homepage Hello
√ GET / return hello world (135 ms)
√ GET /index.html return hello world (16 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.261 s, estimated 2 s
Ran all test suites.
Running tests that fail
Edit the contents of line 12 of the file at /src/__tests__/my-first-test.js
and replace the word “World” with the word “Goodbye” so that instead of looking for "Hello World!"
your test is looking for "Hello Goodbye!"
.
Open up a command line interface.
Ensure that its prompt indicates that your commands will be running within the context of the folder into which you downloaded a copy of my sample codebase.
Run the following command:
npm run test
The output you will see will say something like this, complaining that the homepage, both when accessed as http://localhost:3000/
and as http://localhost:3000/index.html
, says “Hello World” instead of saying “Hello Goodbye as expected by the unit test (after all, you just finished turning your unit test into a Beatles fan):
> test
> cross-env NODE_ENV=test jest --testTimeout=10000 --detectOpenHandles
FAIL src/__tests__/my-first-test.js
Homepage Hello
× GET / return hello world (146 ms)
× GET /index.html return hello world (20 ms)
● Homepage Hello › GET / return hello world
expect(received).toEqual(expected) // deep equality
Expected: "Hello Goodbye!"
Received: "Hello World!"
10 | expect(supertest_response.status).toEqual(200);
11 | expect(supertest_response.type).toEqual('text/html');
> 12 | expect(supertest_response.text).toEqual("Hello Goodbye!");
| ^
13 | }
14 | it("GET / return hello world", async () => {
15 | const res = await supertest(my_express_server).get("/");
at toEqual (src/__tests__/my-first-test.js:12:41)
at Object.check_homepage_expectations (src/__tests__/my-first-test.js:16:9)
● Homepage Hello › GET /index.html return hello world
expect(received).toEqual(expected) // deep equality
Expected: "Hello Goodbye!"
Received: "Hello World!"
10 | expect(supertest_response.status).toEqual(200);
11 | expect(supertest_response.type).toEqual('text/html');
> 12 | expect(supertest_response.text).toEqual("Hello Goodbye!");
| ^
13 | }
14 | it("GET / return hello world", async () => {
15 | const res = await supertest(my_express_server).get("/");
at toEqual (src/__tests__/my-first-test.js:12:41)
at Object.check_homepage_expectations (src/__tests__/my-first-test.js:20:9)
Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total
Snapshots: 0 total
Time: 1.495 s, estimated 2 s
Ran all test suites.
P.S. If leaving a failing test lying around is really bothering you, feel free to either:
- Change “
Goodbye
” back to “World
,” or - Engage in some “test-driven-development” (“TDD”) by updating line 6 of
/src/web/server.js
from “World
” to “Goodbye
.”
Then run npm run test
one more time to set your heart at ease.
Extra credit – build and run
You’re not forbidden from building and running this server just because it’s not the focus of this exercise.
Go ahead – try npm run build
and then node ./dist/server.js
against this codebase just like you did against the last one.
In fact, try it with the server saying “World
” and the test expecting “Goodbye
.”
Did you notice that nothing at all goes wrong with the build process or with visiting your website on localhost
?
(Although when you visit it, it will say “World
,” not “Goodbye
” – remember, that’s what you told the server to do.)
By deliberately running npm run build
and node ./dist/server.js
even when you knew that npm run test
would be angry with you, you’ve arguably “built” a runtime out of “bad code” and chosen to run “bad code” on a webserver.
Luckily, you’ve only done so on your local machine, but as we work our way through this series, we’ll make sure to build codebases and habits that prevent us from running npm run build
if npm run test
failed.
Credits
- Kyle Luke’s Testing your Express.js Backend Server.
- xersiee on StackOverflow enhancing the official tiny Express app example with one more line of code.
- Many GitHub Supertest issue #520 commenters.
Posts in this series
- Part 1 - Source code that builds locally into a Node.js Hello World webapp
- Part 2 - This Article
- Part 3 - Protecting Git branches in Azure DevOps repositories
- Part 4 - Making Azure DevOps Pipelines build a Hello World webapp from Git-tracked source code changes
- Part 5 - Failing Azure DevOps Pipeline builds if unit tests fail
- Part 6 - Logging your command line into Azure
- Part 7 - Provisioning an Azure Resource Group
- Part 8 - Provisioning Azure App Services to host your Hello World webapp
- Part 9 - Provisioning Azure AD Service Principals that can deploy built webapps onto your Azure App Service resources
- Part 10 - Provisioning Azure DevOps Service Connections that let ADO Release Pipelines leverage Azure AD Service Principals for sensitive CI/CD tasks
- Part 11 - Deploying a built webapp onto Azure App Service with ADO Release Pipelines