├── server.js ├── package.json ├── app.js ├── database.js ├── .gitignore └── app.test.js /server.js: -------------------------------------------------------------------------------- 1 | import database from './database.js' 2 | import makeApp from './app.js' 3 | 4 | const app = makeApp(database) 5 | 6 | app.listen(8080, () => console.log("listening on port 8080")) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "express": "^4.17.1" 5 | }, 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "name": "testing_express", 10 | "version": "1.0.0", 11 | "main": "server.js", 12 | "devDependencies": { 13 | "jest": "^26.6.3", 14 | "supertest": "^6.1.3" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "description": "" 20 | } 21 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | 3 | export default function (database) { 4 | const app = express() 5 | 6 | app.use(express.json()) 7 | app.post('/users', async (req, res) => { 8 | const { password, username } = req.body 9 | if (!password || !username) { 10 | res.sendStatus(400) 11 | return 12 | } 13 | 14 | const userId = await database.createUser(username, password) 15 | 16 | res.send({ userId }) 17 | }) 18 | return app 19 | } 20 | -------------------------------------------------------------------------------- /database.js: -------------------------------------------------------------------------------- 1 | import mysql from 'mysql2' 2 | 3 | const connection = mysql.createPool({ 4 | host : 'localhost', 5 | user : 'root', 6 | database : 'some_database' 7 | }) 8 | 9 | export async function getUser(username) { 10 | const [rows] = await connection.promise().query( 11 | `SELECT * 12 | FROM users 13 | WHERE username = ?`, 14 | [username] 15 | ) 16 | 17 | return rows[0] 18 | } 19 | 20 | export async function createUser(username, password) { 21 | const { insertId } = await connection.promise().query( 22 | `INSERT INTO users (username, password) 23 | VALUES (?, ?)`, 24 | [username, password] 25 | ) 26 | 27 | return insertId 28 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | -------------------------------------------------------------------------------- /app.test.js: -------------------------------------------------------------------------------- 1 | import request from 'supertest' 2 | import makeApp from './app.js' 3 | import { jest } from '@jest/globals' 4 | 5 | const createUser = jest.fn() 6 | const getUser = jest.fn() 7 | 8 | const app = makeApp({ 9 | createUser, 10 | getUser 11 | }) 12 | 13 | describe("POST /users", () => { 14 | 15 | beforeEach(() => { 16 | createUser.mockReset() 17 | createUser.mockResolvedValue(0) 18 | }) 19 | 20 | describe("given a username and password", () => { 21 | test("should save the username and password to the database", async () => { 22 | const bodyData = [ 23 | {username: "username1", password: "password1"}, 24 | {username: "username2", password: "password2"}, 25 | {username: "username3", password: "password3"}, 26 | ] 27 | for (const body of bodyData) { 28 | createUser.mockReset() 29 | await request(app).post("/users").send(body) 30 | expect(createUser.mock.calls.length).toBe(1) 31 | expect(createUser.mock.calls[0][0]).toBe(body.username) 32 | expect(createUser.mock.calls[0][1]).toBe(body.password) 33 | } 34 | }) 35 | 36 | test("should respond with a json object containg the user id", async () => { 37 | for (let i = 0; i < 10; i++) { 38 | createUser.mockReset() 39 | createUser.mockResolvedValue(i) 40 | const response = await request(app).post("/users").send({ username: "username", password: "password" }) 41 | expect(response.body.userId).toBe(i) 42 | } 43 | }) 44 | 45 | test("should respond with a 200 status code", async () => { 46 | const response = await request(app).post("/users").send({ 47 | username: "username", 48 | password: "password" 49 | }) 50 | expect(response.statusCode).toBe(200) 51 | }) 52 | test("should specify json in the content type header", async () => { 53 | const response = await request(app).post("/users").send({ 54 | username: "username", 55 | password: "password" 56 | }) 57 | expect(response.headers['content-type']).toEqual(expect.stringContaining("json")) 58 | }) 59 | test("response has userId", async () => { 60 | const response = await request(app).post("/users").send({ 61 | username: "username", 62 | password: "password" 63 | }) 64 | expect(response.body.userId).toBeDefined() 65 | }) 66 | }) 67 | 68 | describe("when the username and password is missing", () => { 69 | test("should respond with a status code of 400", async () => { 70 | const bodyData = [ 71 | {username: "username"}, 72 | {password: "password"}, 73 | {} 74 | ] 75 | for (const body of bodyData) { 76 | const response = await request(app).post("/users").send(body) 77 | expect(response.statusCode).toBe(400) 78 | } 79 | }) 80 | }) 81 | 82 | }) --------------------------------------------------------------------------------