Skip to content

Commit 717abc5

Browse files
authored
Merge pull request #1 from sixoverground/part2
unit + integration tests
2 parents e2c13c1 + b7bd971 commit 717abc5

File tree

10 files changed

+3135
-53
lines changed

10 files changed

+3135
-53
lines changed

app.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const bodyParser = require('body-parser')
2+
const express = require('express')
3+
const knex = require('knex')
4+
const methodOverride = require('method-override')
5+
const { Model } = require('objection')
6+
const knexConfig = require('./knexfile')
7+
const router = require('./routes')
8+
9+
const app = express()
10+
11+
const db = knex(knexConfig[process.env.NODE_ENV || 'development'])
12+
Model.knex(db)
13+
14+
app.set('view engine', 'pug')
15+
16+
app.use(bodyParser.urlencoded({ extended: false }))
17+
18+
app.use(methodOverride((req, res) => {
19+
if (req.body && typeof req.body === 'object' && '_method' in req.body) {
20+
var method = req.body._method
21+
delete req.body._method
22+
return method
23+
}
24+
}))
25+
26+
app.use('/', router)
27+
28+
module.exports = {
29+
app,
30+
db
31+
}

index.js

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,5 @@
1-
const bodyParser = require('body-parser')
2-
const express = require('express')
3-
const knex = require('knex')
4-
const methodOverride = require('method-override')
5-
const { Model } = require('objection')
6-
const knexConfig = require('./knexfile')
7-
const router = require('./routes')
8-
9-
const app = express()
1+
const { app } = require('./app')
2+
103
const port = 3000
114

12-
const db = knex(knexConfig[process.env.NODE_ENV || 'development'])
13-
Model.knex(db)
14-
15-
app.set('view engine', 'pug')
16-
17-
app.use(bodyParser.urlencoded({ extended: false }))
18-
19-
app.use(methodOverride((req, res) => {
20-
if (req.body && typeof req.body === 'object' && '_method' in req.body) {
21-
var method = req.body._method
22-
delete req.body._method
23-
return method
24-
}
25-
}))
26-
27-
app.use('/', router)
28-
29-
app.listen(port, () => console.log(`My blog is listening on port ${port}!`))
5+
app.listen(port, () => console.log(`My blog is listening on port ${port}!`))

jest.config.js

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// For a detailed explanation regarding each configuration property, visit:
2+
// https://jestjs.io/docs/en/configuration.html
3+
4+
module.exports = {
5+
// All imported modules in your tests should be mocked automatically
6+
// automock: false,
7+
8+
// Stop running tests after `n` failures
9+
// bail: 0,
10+
11+
// Respect "browser" field in package.json when resolving modules
12+
// browser: false,
13+
14+
// The directory where Jest should store its cached dependency information
15+
// cacheDirectory: "/private/var/folders/f7/9_bh8fl93w5gpjd2z7b87ksh0000gn/T/jest_dx",
16+
17+
// Automatically clear mock calls and instances between every test
18+
clearMocks: true,
19+
20+
// Indicates whether the coverage information should be collected while executing the test
21+
// collectCoverage: false,
22+
23+
// An array of glob patterns indicating a set of files for which coverage information should be collected
24+
// collectCoverageFrom: null,
25+
26+
// The directory where Jest should output its coverage files
27+
coverageDirectory: "coverage",
28+
29+
// An array of regexp pattern strings used to skip coverage collection
30+
// coveragePathIgnorePatterns: [
31+
// "/node_modules/"
32+
// ],
33+
34+
// A list of reporter names that Jest uses when writing coverage reports
35+
// coverageReporters: [
36+
// "json",
37+
// "text",
38+
// "lcov",
39+
// "clover"
40+
// ],
41+
42+
// An object that configures minimum threshold enforcement for coverage results
43+
// coverageThreshold: null,
44+
45+
// A path to a custom dependency extractor
46+
// dependencyExtractor: null,
47+
48+
// Make calling deprecated APIs throw helpful error messages
49+
// errorOnDeprecated: false,
50+
51+
// Force coverage collection from ignored files using an array of glob patterns
52+
// forceCoverageMatch: [],
53+
54+
// A path to a module which exports an async function that is triggered once before all test suites
55+
// globalSetup: null,
56+
57+
// A path to a module which exports an async function that is triggered once after all test suites
58+
// globalTeardown: null,
59+
60+
// A set of global variables that need to be available in all test environments
61+
// globals: {},
62+
63+
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
64+
// maxWorkers: "50%",
65+
66+
// An array of directory names to be searched recursively up from the requiring module's location
67+
// moduleDirectories: [
68+
// "node_modules"
69+
// ],
70+
71+
// An array of file extensions your modules use
72+
// moduleFileExtensions: [
73+
// "js",
74+
// "json",
75+
// "jsx",
76+
// "ts",
77+
// "tsx",
78+
// "node"
79+
// ],
80+
81+
// A map from regular expressions to module names that allow to stub out resources with a single module
82+
// moduleNameMapper: {},
83+
84+
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
85+
// modulePathIgnorePatterns: [],
86+
87+
// Activates notifications for test results
88+
// notify: false,
89+
90+
// An enum that specifies notification mode. Requires { notify: true }
91+
// notifyMode: "failure-change",
92+
93+
// A preset that is used as a base for Jest's configuration
94+
// preset: null,
95+
96+
// Run tests from one or more projects
97+
// projects: null,
98+
99+
// Use this configuration option to add custom reporters to Jest
100+
// reporters: undefined,
101+
102+
// Automatically reset mock state between every test
103+
// resetMocks: false,
104+
105+
// Reset the module registry before running each individual test
106+
// resetModules: false,
107+
108+
// A path to a custom resolver
109+
// resolver: null,
110+
111+
// Automatically restore mock state between every test
112+
// restoreMocks: false,
113+
114+
// The root directory that Jest should scan for tests and modules within
115+
// rootDir: null,
116+
117+
// A list of paths to directories that Jest should use to search for files in
118+
// roots: [
119+
// "<rootDir>"
120+
// ],
121+
122+
// Allows you to use a custom runner instead of Jest's default test runner
123+
// runner: "jest-runner",
124+
125+
// The paths to modules that run some code to configure or set up the testing environment before each test
126+
// setupFiles: [],
127+
128+
// A list of paths to modules that run some code to configure or set up the testing framework before each test
129+
// setupFilesAfterEnv: [],
130+
131+
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
132+
// snapshotSerializers: [],
133+
134+
// The test environment that will be used for testing
135+
testEnvironment: "node",
136+
137+
// Options that will be passed to the testEnvironment
138+
// testEnvironmentOptions: {},
139+
140+
// Adds a location field to test results
141+
// testLocationInResults: false,
142+
143+
// The glob patterns Jest uses to detect test files
144+
// testMatch: [
145+
// "**/__tests__/**/*.[jt]s?(x)",
146+
// "**/?(*.)+(spec|test).[tj]s?(x)"
147+
// ],
148+
149+
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
150+
// testPathIgnorePatterns: [
151+
// "/node_modules/"
152+
// ],
153+
154+
// The regexp pattern or array of patterns that Jest uses to detect test files
155+
// testRegex: [],
156+
157+
// This option allows the use of a custom results processor
158+
// testResultsProcessor: null,
159+
160+
// This option allows use of a custom test runner
161+
// testRunner: "jasmine2",
162+
163+
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
164+
// testURL: "http://localhost",
165+
166+
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
167+
// timers: "real",
168+
169+
// A map from regular expressions to paths to transformers
170+
// transform: null,
171+
172+
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
173+
// transformIgnorePatterns: [
174+
// "/node_modules/"
175+
// ],
176+
177+
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
178+
// unmockedModulePathPatterns: undefined,
179+
180+
// Indicates whether each individual test should be reported during the run
181+
// verbose: null,
182+
183+
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
184+
// watchPathIgnorePatterns: [],
185+
186+
// Whether to use watchman for file crawling
187+
// watchman: true,
188+
};

knexfile.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ module.exports = {
33
client: 'pg',
44
connection: {
55
database: 'blog_development'
6+
},
7+
seeds: {
8+
directory: './seeds/development'
9+
}
10+
},
11+
test: {
12+
client: 'pg',
13+
connection: {
14+
database: 'blog_test'
15+
},
16+
seeds: {
17+
directory: './seeds/test'
618
}
719
}
820
}

models/Article.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ class Article extends BaseModel {
44
static get tableName() {
55
return 'articles'
66
}
7+
8+
static get jsonSchema() {
9+
return {
10+
type: 'object',
11+
required: ['title'],
12+
properties: {
13+
id: { type: 'integer' },
14+
title: { type: 'string', minLength: 1, maxLength: 255 },
15+
text: { type: 'string' }
16+
}
17+
}
18+
}
719
}
820

921
module.exports = Article

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"main": "index.js",
55
"license": "MIT",
66
"scripts": {
7-
"start": "node index.js"
7+
"start": "node index.js",
8+
"test": "jest --runInBand --detectOpenHandles --coverage"
89
},
910
"dependencies": {
1011
"body-parser": "^1.19.0",
@@ -14,5 +15,9 @@
1415
"objection": "^1.6.9",
1516
"pg": "^7.11.0",
1617
"pug": "^2.0.3"
18+
},
19+
"devDependencies": {
20+
"jest": "^24.9.0",
21+
"supertest": "^4.0.2"
1722
}
1823
}

seeds/test/seed.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
exports.seed = async (knex) => {
3+
await knex('articles').del()
4+
await knex('articles').insert([
5+
{ title: 'First Article!', text: 'This is my first article.' },
6+
])
7+
}

test/integration/articles.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const request = require('supertest')
2+
const { app, db } = require('../../app')
3+
const Article = require('../../models/Article')
4+
5+
describe('/articles', () => {
6+
beforeEach(async () => {
7+
await db.migrate.rollback()
8+
await db.migrate.latest()
9+
await db.seed.run()
10+
})
11+
12+
afterEach(async () => {
13+
await db.migrate.rollback()
14+
})
15+
16+
describe('GET /articles/new', () => {
17+
test('can enter a new article', async () => {
18+
const response = await request(app)
19+
.get('/articles/new')
20+
expect(response.status).toBe(200)
21+
})
22+
})
23+
24+
describe('POST /articles', () => {
25+
test('can create an article', async () => {
26+
const response = await request(app)
27+
.post('/articles')
28+
.send('title=Can Create&body=Article successfully.')
29+
expect(response.status).toBe(302)
30+
const articles = await Article.query().orderBy('id')
31+
const article = articles[articles.length - 1]
32+
expect(response.header.location).toBe(`/articles/${article.id}`)
33+
})
34+
})
35+
})

test/models/Article.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const knex = require('knex')
2+
const { Model } = require('objection')
3+
const knexConfig = require('../../knexfile')
4+
const Article = require('../../models/Article')
5+
6+
const db = knex(knexConfig[process.env.NODE_ENV || 'test'])
7+
Model.knex(db)
8+
9+
describe('Article', () => {
10+
let article
11+
12+
beforeEach(async () => {
13+
await db.migrate.rollback()
14+
await db.migrate.latest()
15+
await db.seed.run()
16+
17+
article = await Article.query().findOne({ title: 'First Article!' })
18+
})
19+
20+
afterEach(async () => {
21+
await db.migrate.rollback()
22+
})
23+
24+
test('has a valid seed', async () => {
25+
expect(article).toBeDefined()
26+
})
27+
28+
test('titles are required', async () => {
29+
article.title = undefined
30+
expect(() => {
31+
article.$validate()
32+
}).toThrow()
33+
})
34+
35+
})

0 commit comments

Comments
 (0)