chore(tests): Migrate to Jest

* Add jest, remove mocha/expect

* Migrate all tests to Jest

* Make test pass

* Move tests into `/test/`

* Update testing docs

* Fix resolver test
This commit is contained in:
Jason Etcovitch 2017-10-14 06:02:34 -07:00 committed by GitHub
parent 6c58a9742b
commit e7a0c5cd80
11 changed files with 76 additions and 5640 deletions

View File

@ -6,11 +6,9 @@ next: docs/pagination.md
We highly recommend working in the style of [test-driven development](http://agiledata.org/essays/tdd.html) when creating probot apps. It frustrating to constantly create real GitHub events in order to test a app. Redelivering webhooks is possible and can be accessed in your app's [settings](https://github.com/settings/apps) page under the **Advanced** tab. We do offer the above documented `simulate` method to help make this easier; however, by writing your tests first, you can avoid repeatedly recreating actual events from GitHub to check if your code is working.
For our testing examples, we use [mocha](https://mochajs.org/) and [expect](https://github.com/mjackson/expect), but there are other options that can perform similar operations. Here's an example of creating a robot instance and mocking out the GitHub API:
For our testing examples, we use [jest](https://facebook.github.io/jest/), but there are other options that can perform similar operations. Here's an example of creating a robot instance and mocking out the GitHub API:
```js
// Requiring our testing framework
const expect = require('expect')
// Requiring probot allows us to mock out a robot instance
const {createRobot} = require('probot')
// Requireing our app
@ -31,7 +29,7 @@ describe('your-app', () => {
// This is an easy way to mock out the GitHub API
github = {
issues: {
createComment: expect.createSpy().andReturn(Promise.resolve({
createComment: jest.fn().mockReturnValue(Promise.resolve({
// Whatever the GitHub API should return
}))
}

5552
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,12 +9,22 @@
},
"scripts": {
"start": "node ./bin/probot run",
"test": "mocha && standard && npm run doc-lint",
"test": "jest && standard && npm run doc-lint",
"doc-lint": "standard docs/**/*.md",
"doc": "jsdoc -c .jsdoc.json",
"postpublish": "script/publish-docs",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"jest": {
"testMatch": [ "**/test/**/*.js?(x)", "**/?(*.)(spec|test).js?(x)" ],
"modulePathIgnorePatterns": [
"<rootDir>/test/fixtures/",
"<rootDir>/test/setup.js"
],
"setupFiles": [
"<rootDir>/test/setup.js"
]
},
"author": "Brandon Keepers",
"license": "ISC",
"dependencies": {
@ -29,6 +39,7 @@
"github": "^10.1.0",
"github-app": "^3.2.0",
"github-webhook-handler": "github:rvagg/github-webhook-handler#v0.6.1",
"jest": "^21.2.1",
"js-yaml": "^3.9.1",
"pkg-conf": "^2.0.0",
"promise-events": "^0.1.3",
@ -39,11 +50,9 @@
"devDependencies": {
"eslint": "^4.6.1",
"eslint-plugin-markdown": "^1.0.0-beta.6",
"expect": "^1.20.2",
"jsdoc": "^3.5.5",
"jsdoc-strip-async-await": "^0.1.0",
"minami": "^1.1.1",
"mocha": "^3.5.0",
"nock": "^9.0.19",
"semantic-release": "^8.0.3",
"standard": "^10.0.3",
@ -51,7 +60,7 @@
},
"standard": {
"env": [
"mocha"
"jest"
],
"plugins": [
"markdown"

View File

@ -1,6 +1,5 @@
const fs = require('fs')
const path = require('path')
const expect = require('expect')
const Context = require('../lib/context')
describe('Context', function () {
@ -82,7 +81,7 @@ describe('Context', function () {
beforeEach(function () {
github = {
repos: {
getContent: expect.createSpy()
getContent: jest.fn()
}
}
@ -90,7 +89,7 @@ describe('Context', function () {
})
it('gets a valid configuration', async function () {
github.repos.getContent.andReturn(Promise.resolve(readConfig('basic.yml')))
github.repos.getContent = jest.fn().mockReturnValue(Promise.resolve(readConfig('basic.yml')))
const config = await context.config('test-file.yml')
expect(github.repos.getContent).toHaveBeenCalledWith({
@ -108,7 +107,7 @@ describe('Context', function () {
it('returns null when the file is missing', async function () {
const error = new Error('An error occurred')
error.code = 404
github.repos.getContent.andReturn(Promise.reject(error))
github.repos.getContent = jest.fn().mockReturnValue(Promise.reject(error))
expect(await context.config('test-file.yml')).toBe(null)
})
@ -116,7 +115,7 @@ describe('Context', function () {
it('returns the default config when the file is missing and default config is passed', async function () {
const error = new Error('An error occurred')
error.code = 404
github.repos.getContent.andReturn(Promise.reject(error))
github.repos.getContent = jest.fn().mockReturnValue(Promise.reject(error))
const defaultConfig = {
foo: 5,
bar: 7,
@ -127,7 +126,7 @@ describe('Context', function () {
})
it('throws when the configuration file is malformed', async function () {
github.repos.getContent.andReturn(Promise.resolve(readConfig('malformed.yml')))
github.repos.getContent = jest.fn().mockReturnValue(Promise.resolve(readConfig('malformed.yml')))
let e
let contents
@ -137,13 +136,13 @@ describe('Context', function () {
e = err
}
expect(contents).toNotExist()
expect(e).toExist()
expect(contents).toBeUndefined()
expect(e).toBeDefined()
expect(e.message).toMatch(/^end of the stream or a document separator/)
})
it('throws when loading unsafe yaml', async function () {
github.repos.getContent.andReturn(readConfig('evil.yml'))
github.repos.getContent = jest.fn().mockReturnValue(readConfig('evil.yml'))
let e
let config
@ -153,13 +152,13 @@ describe('Context', function () {
e = err
}
expect(config).toNotExist()
expect(e).toExist()
expect(config).toBeUndefined()
expect(e).toBeDefined()
expect(e.message).toMatch(/unknown tag/)
})
it('returns an empty object when the file is empty', async function () {
github.repos.getContent.andReturn(readConfig('empty.yml'))
github.repos.getContent = jest.fn().mockReturnValue(readConfig('empty.yml'))
const contents = await context.config('test-file.yml')
@ -167,7 +166,7 @@ describe('Context', function () {
})
it('overwrites default config settings', async function () {
github.repos.getContent.andReturn(Promise.resolve(readConfig('basic.yml')))
github.repos.getContent = jest.fn().mockReturnValue(Promise.resolve(readConfig('basic.yml')))
const config = await context.config('test-file.yml', {foo: 10})
expect(github.repos.getContent).toHaveBeenCalledWith({
@ -183,7 +182,7 @@ describe('Context', function () {
})
it('uses default settings to fill in missing options', async function () {
github.repos.getContent.andReturn(Promise.resolve(readConfig('missing.yml')))
github.repos.getContent = jest.fn().mockReturnValue(Promise.resolve(readConfig('missing.yml')))
const config = await context.config('test-file.yml', {bar: 7})
expect(github.repos.getContent).toHaveBeenCalledWith({

View File

@ -1,4 +1,3 @@
const expect = require('expect')
const createProbot = require('..')
describe('Probot', () => {
@ -17,7 +16,7 @@ describe('Probot', () => {
describe('webhook delivery', () => {
it('forwards webhooks to the robot', async () => {
const robot = probot.load(() => {})
robot.receive = expect.createSpy()
robot.receive = jest.fn()
probot.webhook.emit('*', event)
expect(robot.receive).toHaveBeenCalledWith(event)
})
@ -105,9 +104,9 @@ describe('Probot', () => {
describe('receive', () => {
it('forwards events to each plugin', async () => {
const spy = expect.createSpy()
const spy = jest.fn()
const robot = probot.load(robot => robot.on('push', spy))
robot.auth = expect.createSpy().andReturn(Promise.resolve({}))
robot.auth = jest.fn().mockReturnValue(Promise.resolve({}))
await probot.receive(event)

View File

@ -1,2 +0,0 @@
--recursive
--require ./test/setup

View File

@ -1,6 +1,6 @@
const fs = require('fs')
const expect = require('expect')
const readFileSync = fs.readFileSync
const readdirSync = fs.readdirSync
const {findPrivateKey} = require('../lib/private-key')
@ -11,25 +11,22 @@ describe('private-key', function () {
beforeEach(function () {
privateKey = 'I AM PRIVET KEY!?!!~1!'
keyfilePath = '/some/path'
expect.spyOn(fs, 'readFileSync')
.andReturn(privateKey)
fs.readFileSync = jest.fn().mockReturnValue(privateKey)
})
afterEach(function () {
expect.restoreSpies()
fs.readFileSync = readFileSync
})
describe('findPrivateKey()', function () {
describe('when a filepath is provided', function () {
it('should read the file at given filepath', function () {
findPrivateKey(keyfilePath)
expect(fs.readFileSync)
.toHaveBeenCalledWith(keyfilePath)
expect(fs.readFileSync).toHaveBeenCalledWith(keyfilePath)
})
it('should return the key', function () {
expect(findPrivateKey(keyfilePath))
.toEqual(privateKey)
expect(findPrivateKey(keyfilePath)).toEqual(privateKey)
})
})
@ -43,8 +40,7 @@ describe('private-key', function () {
})
it('should return the key', function () {
expect(findPrivateKey())
.toEqual(privateKey)
expect(findPrivateKey()).toEqual(privateKey)
})
})
@ -58,8 +54,7 @@ describe('private-key', function () {
})
it('should return the key', function () {
expect(findPrivateKey())
.toEqual('line 1\nline 2')
expect(findPrivateKey()).toEqual('line 1\nline 2')
})
})
@ -74,47 +69,41 @@ describe('private-key', function () {
it('should read the file at given filepath', function () {
findPrivateKey()
expect(fs.readFileSync)
.toHaveBeenCalledWith(keyfilePath)
expect(fs.readFileSync).toHaveBeenCalledWith(keyfilePath)
})
it('should return the key', function () {
expect(findPrivateKey())
.toEqual(privateKey)
expect(findPrivateKey()).toEqual(privateKey)
})
})
describe('when no private key is provided', function () {
beforeEach(function () {
expect.spyOn(fs, 'readdirSync')
.andReturn([
'foo.txt',
'foo.pem'
])
fs.readdirSync = jest.fn().mockReturnValue([
'foo.txt',
'foo.pem'
])
})
it('should look for one in the current directory', function () {
findPrivateKey()
expect(fs.readdirSync)
.toHaveBeenCalledWith(process.cwd())
expect(fs.readdirSync).toHaveBeenCalledWith(process.cwd())
})
describe('and a key file is present', function () {
it('should load the key file', function () {
findPrivateKey()
expect(fs.readFileSync)
.toHaveBeenCalledWith('foo.pem')
expect(fs.readFileSync).toHaveBeenCalledWith('foo.pem')
})
})
describe('and a key file is not present', function () {
beforeEach(function () {
fs.readdirSync.restore()
fs.readdirSync = readdirSync
})
it('should throw an error', function () {
expect(findPrivateKey)
.toThrow(Error, /missing private key for GitHub App/i)
expect(findPrivateKey).toThrow(/missing private key for GitHub App/i)
})
})
})

View File

@ -1,19 +1,16 @@
/* eslint prefer-arrow-callback: off */
const expect = require('expect')
const resolve = require('../lib/resolver')
const stubPluginPath = require.resolve('./fixtures/plugin/stub-plugin')
const basedir = process.cwd()
describe('resolver', function () {
describe('resolver', () => {
let stubResolver
beforeEach(function () {
stubResolver = expect.createSpy().andReturn(stubPluginPath)
beforeEach(() => {
stubResolver = jest.fn().mockReturnValue(stubPluginPath)
})
it('loads the module at the resolved path', function () {
it('loads the module at the resolved path', () => {
const module = resolve('foo', {resolver: stubResolver})
expect(module).toBe(require(stubPluginPath))
expect(stubResolver).toHaveBeenCalledWith('foo', {basedir})

View File

@ -1,11 +1,9 @@
const expect = require('expect')
const Context = require('../lib/context')
const createRobot = require('../lib/robot')
describe('Robot', function () {
let robot
let event
let spy
beforeEach(function () {
robot = createRobot()
@ -18,19 +16,17 @@ describe('Robot', function () {
installation: {id: 1}
}
}
spy = expect.createSpy()
})
describe('constructor', () => {
it('takes a logger', () => {
const logger = {
trace: expect.createSpy(),
debug: expect.createSpy(),
info: expect.createSpy(),
warn: expect.createSpy(),
error: expect.createSpy(),
fatal: expect.createSpy()
trace: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
fatal: jest.fn()
}
robot = createRobot({logger})
@ -41,16 +37,18 @@ describe('Robot', function () {
describe('on', function () {
it('calls callback when no action is specified', async function () {
const spy = jest.fn()
robot.on('test', spy)
expect(spy).toNotHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(0)
await robot.receive(event)
expect(spy).toHaveBeenCalled()
expect(spy.calls[0].arguments[0]).toBeA(Context)
expect(spy.calls[0].arguments[0].payload).toBe(event.payload)
expect(spy.mock.calls[0][0]).toBeInstanceOf(Context)
expect(spy.mock.calls[0][0].payload).toBe(event.payload)
})
it('calls callback with same action', async function () {
const spy = jest.fn()
robot.on('test.foo', spy)
await robot.receive(event)
@ -58,13 +56,15 @@ describe('Robot', function () {
})
it('does not call callback with different action', async function () {
const spy = jest.fn()
robot.on('test.nope', spy)
await robot.receive(event)
expect(spy).toNotHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(0)
})
it('calls callback with *', async function () {
const spy = jest.fn()
robot.on('*', spy)
await robot.receive(event)
@ -80,17 +80,18 @@ describe('Robot', function () {
}
}
const spy = jest.fn()
robot.on(['test.foo', 'arrayTest.bar'], spy)
await robot.receive(event)
await robot.receive(event2)
expect(spy.calls.length).toEqual(2)
expect(spy.mock.calls.length).toEqual(2)
})
})
describe('receive', () => {
it('delivers the event', async () => {
const spy = expect.createSpy()
const spy = jest.fn()
robot.on('test', spy)
await robot.receive(event)
@ -99,7 +100,7 @@ describe('Robot', function () {
})
it('waits for async events to resolve', async () => {
const spy = expect.createSpy()
const spy = jest.fn()
robot.on('test', () => {
return new Promise(resolve => {
@ -134,7 +135,7 @@ describe('Robot', function () {
beforeEach(() => {
error = new Error('testing')
robot.log.error = expect.createSpy()
robot.log.error = jest.fn()
})
it('logs errors thrown from handlers', async () => {
@ -148,7 +149,7 @@ describe('Robot', function () {
// Expected
}
const arg = robot.log.error.calls[0].arguments[0]
const arg = robot.log.error.mock.calls[0][0]
expect(arg.err).toBe(error)
expect(arg.event).toBe(event)
})
@ -163,7 +164,7 @@ describe('Robot', function () {
}
expect(robot.log.error).toHaveBeenCalled()
const arg = robot.log.error.calls[0].arguments[0]
const arg = robot.log.error.mock.calls[0][0]
expect(arg.err).toBe(error)
expect(arg.event).toBe(event)
})

View File

@ -1,4 +1,3 @@
const expect = require('expect')
const serializers = require('../lib/serializers')
describe('serializers', () => {

View File

@ -1,4 +1,3 @@
const expect = require('expect')
const request = require('supertest')
const createServer = require('../lib/server')
@ -7,7 +6,7 @@ describe('server', function () {
let webhook
beforeEach(() => {
webhook = expect.createSpy().andCall((req, res, next) => next())
webhook = jest.fn((req, res, next) => next())
server = createServer(webhook)
// Error handler to avoid printing logs
@ -24,7 +23,7 @@ describe('server', function () {
describe('webhook handler', () => {
it('should 500 on a webhook error', () => {
webhook.andCall((req, res, callback) => callback(new Error('webhook error')))
webhook.mockImplementation((req, res, callback) => callback(new Error('webhook error')))
return request(server).post('/').expect(500)
})
})