├── 1-installing-jest ├── .babelrc └── package.json ├── 10-building-a-library--all ├── model.js └── model.spec.js ├── 11-building-a-library--find ├── model.js └── model.spec.js ├── 12-building-a-library--update ├── model.js └── model.spec.js ├── 13-building-a-library--customize ├── model.js └── model.spec.js ├── 2-running-our-first-test ├── user.js └── user.spec.js ├── 3-expectations └── expectations.spec.js ├── 4-snapshot-testing └── snapshots.spec.js ├── 5-execute-code-before-and-after ├── movies.js └── movies.spec.js ├── 8-building-a-library ├── model.js └── model.spec.js ├── 9-building-a-library--record ├── model.js └── model.spec.js └── README.md /1-installing-jest/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /1-installing-jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "yarn jest" 4 | }, 5 | "devDependencies": { 6 | "babel-core": "^6.26.3", 7 | "babel-jest": "^23.6.0", 8 | "babel-preset-env": "^1.7.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /10-building-a-library--all/model.js: -------------------------------------------------------------------------------- 1 | export default class Model { 2 | 3 | constructor(data = []) { 4 | this.$collection = [] 5 | 6 | if(data.length) 7 | this.record(data) 8 | } 9 | 10 | record(data) { 11 | this.$collection.push(...data) 12 | } 13 | all() { 14 | return this.$collection.map(entry => Object.assign({}, entry)) 15 | } 16 | update() {} 17 | find() {} 18 | } 19 | -------------------------------------------------------------------------------- /10-building-a-library--all/model.spec.js: -------------------------------------------------------------------------------- 1 | import Model from './model' 2 | 3 | test('new works', () => { 4 | expect(new Model).toBeInstanceOf(Model) 5 | }) 6 | 7 | test('model structure', () => { 8 | expect(new Model).toEqual(expect.objectContaining({ 9 | $collection: expect.any(Array), 10 | record: expect.any(Function), 11 | all: expect.any(Function), 12 | find: expect.any(Function), 13 | update: expect.any(Function), 14 | })) 15 | }) 16 | 17 | describe('record', () => { 18 | const heroes = [{name: 'Batman'}, { name: 'Black Panther'}] 19 | 20 | test('can add data to the collection', () => { 21 | const model = new Model() 22 | model.record(heroes) 23 | expect(model.$collection).toEqual(heroes) 24 | }) 25 | 26 | test('gets called when data is passed to Model', () => { 27 | const spy = jest.spyOn(Model.prototype, 'record') 28 | const model = new Model(heroes) 29 | expect(spy).toHaveBeenCalled() 30 | spy.mockRestore() 31 | }) 32 | }) 33 | 34 | describe('all', () => { 35 | 36 | test('returns empty model', () => { 37 | const model = new Model() 38 | expect(model.all()).toEqual([]) 39 | }) 40 | 41 | test('returns model data', () => { 42 | const model = new Model([{ name: 'Batman'}, { name: 'Joker' }]) 43 | expect(model.all().length).toBe(2) 44 | }) 45 | 46 | test('original data stays intact', () => { 47 | const model = new Model([{ name: 'Batman' }]) 48 | const data = model.all() 49 | data[0].name = 'Joker' 50 | 51 | expect(model.$collection[0].name).toBe('Batman') 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /11-building-a-library--find/model.js: -------------------------------------------------------------------------------- 1 | export default class Model { 2 | 3 | constructor(data = []) { 4 | this.$collection = [] 5 | 6 | if(data.length) 7 | this.record(data) 8 | } 9 | 10 | record(data) { 11 | const primaryKey = 'id' 12 | const mappedData = data.map(entry => { 13 | if(entry[primaryKey]) return entry 14 | entry[primaryKey] = Date.now() 15 | return entry 16 | }) 17 | this.$collection.push(...mappedData) 18 | } 19 | all() { 20 | return this.$collection.map(entry => Object.assign({}, entry)) 21 | } 22 | update() {} 23 | find(value) { 24 | const primaryKey = 'id' 25 | const entry = this.$collection.find(entry => entry[primaryKey] === value) 26 | 27 | return entry ? Object.assign({}, entry) : null 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /11-building-a-library--find/model.spec.js: -------------------------------------------------------------------------------- 1 | import Model from './model' 2 | 3 | test('new works', () => { 4 | expect(new Model).toBeInstanceOf(Model) 5 | }) 6 | 7 | test('model structure', () => { 8 | expect(new Model).toEqual(expect.objectContaining({ 9 | $collection: expect.any(Array), 10 | record: expect.any(Function), 11 | all: expect.any(Function), 12 | find: expect.any(Function), 13 | update: expect.any(Function), 14 | })) 15 | }) 16 | 17 | describe('record', () => { 18 | const heroes = [{id: 1, name: 'Batman'}, { name: 'Black Panther'}] 19 | 20 | test('can add data to the collection', () => { 21 | const model = new Model() 22 | model.record(heroes) 23 | expect(model.$collection).toEqual([ 24 | heroes[0], 25 | { 26 | id: expect.any(Number), 27 | name: heroes[1].name 28 | } 29 | ]) 30 | }) 31 | 32 | test('gets called when data is passed to Model', () => { 33 | const spy = jest.spyOn(Model.prototype, 'record') 34 | const model = new Model(heroes) 35 | expect(spy).toHaveBeenCalled() 36 | spy.mockRestore() 37 | }) 38 | }) 39 | 40 | describe('all', () => { 41 | test('returns empty model', () => { 42 | const model = new Model() 43 | expect(model.all()).toEqual([]) 44 | }) 45 | 46 | test('returns model data', () => { 47 | const model = new Model([{ name: 'Batman'}, { name: 'Joker' }]) 48 | expect(model.all().length).toBe(2) 49 | }) 50 | 51 | test('original data stays intact', () => { 52 | const model = new Model([{ name: 'Batman' }]) 53 | const data = model.all() 54 | data[0].name = 'Joker' 55 | 56 | expect(model.$collection[0].name).toBe('Batman') 57 | }) 58 | }) 59 | 60 | 61 | describe('find', () => { 62 | const heroes = [{ id: 1, name: 'Batman' }, { name: 'Black Panther' }] 63 | 64 | test('returns null if nothing matches', () => { 65 | const model = new Model() 66 | expect(model.find(1)).toEqual(null) 67 | }) 68 | 69 | test('find returns a matching entry', () => { 70 | const model = new Model(heroes) 71 | expect(model.find(1)).toEqual(heroes[0]) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /12-building-a-library--update/model.js: -------------------------------------------------------------------------------- 1 | export default class Model { 2 | constructor(data = []) { 3 | this.$collection = [] 4 | 5 | if (data.length) this.record(data) 6 | } 7 | 8 | record(data) { 9 | const primaryKey = 'id' 10 | const mappedData = data.map(entry => { 11 | if (entry[primaryKey]) return entry 12 | entry[primaryKey] = Date.now() 13 | return entry 14 | }) 15 | this.$collection.push(...mappedData) 16 | } 17 | all() { 18 | return this.$collection.map(entry => Object.assign({}, entry)) 19 | } 20 | 21 | update(key, data) { 22 | const primaryKey = 'id' 23 | const index = this.$collection.findIndex(entry => entry[primaryKey] === key) 24 | if (index < 0) return false 25 | this.$collection.splice( 26 | index, 27 | 1, 28 | Object.assign(this.$collection[index], data) 29 | ) 30 | } 31 | 32 | find(key) { 33 | const primaryKey = 'id' 34 | const entry = this.$collection.find(entry => entry[primaryKey] === key) 35 | 36 | return entry ? Object.assign({}, entry) : null 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /12-building-a-library--update/model.spec.js: -------------------------------------------------------------------------------- 1 | import Model from './model' 2 | 3 | test('new works', () => { 4 | expect(new Model()).toBeInstanceOf(Model) 5 | }) 6 | 7 | test('model structure', () => { 8 | expect(new Model()).toEqual( 9 | expect.objectContaining({ 10 | $collection: expect.any(Array), 11 | record: expect.any(Function), 12 | all: expect.any(Function), 13 | find: expect.any(Function), 14 | update: expect.any(Function) 15 | }) 16 | ) 17 | }) 18 | 19 | describe('record', () => { 20 | const heroes = [{ id: 1, name: 'Batman' }, { name: 'Black Panther' }] 21 | 22 | test('can add data to the collection', () => { 23 | const model = new Model() 24 | model.record(heroes) 25 | expect(model.$collection).toEqual([ 26 | heroes[0], 27 | { 28 | id: expect.any(Number), 29 | name: heroes[1].name 30 | } 31 | ]) 32 | }) 33 | 34 | test('gets called when data is passed to Model', () => { 35 | const spy = jest.spyOn(Model.prototype, 'record') 36 | const model = new Model(heroes) 37 | expect(spy).toHaveBeenCalled() 38 | spy.mockRestore() 39 | }) 40 | }) 41 | 42 | describe('all', () => { 43 | test('returns empty model', () => { 44 | const model = new Model() 45 | expect(model.all()).toEqual([]) 46 | }) 47 | 48 | test('returns model data', () => { 49 | const model = new Model([{ name: 'Batman' }, { name: 'Joker' }]) 50 | expect(model.all().length).toBe(2) 51 | }) 52 | 53 | test('original data stays intact', () => { 54 | const model = new Model([{ name: 'Batman' }]) 55 | const data = model.all() 56 | data[0].name = 'Joker' 57 | 58 | expect(model.$collection[0].name).toBe('Batman') 59 | }) 60 | }) 61 | 62 | describe('find', () => { 63 | const heroes = [{ id: 1, name: 'Batman' }, { name: 'Black Panther' }] 64 | 65 | test('returns null if nothing matches', () => { 66 | const model = new Model() 67 | expect(model.find(1)).toEqual(null) 68 | }) 69 | 70 | test('find returns a matching entry', () => { 71 | const model = new Model(heroes) 72 | expect(model.find(1)).toEqual(heroes[0]) 73 | }) 74 | }) 75 | 76 | describe('update', () => { 77 | const heroesAndVillains = [{ id: 1, name: 'Batman' }] 78 | let model 79 | 80 | beforeEach(() => { 81 | const dataset = JSON.parse(JSON.stringify(heroesAndVillains)) 82 | model = new Model(dataset) 83 | }) 84 | 85 | test('an entry by id', () => { 86 | model.update(1, { name: 'Joker' }) 87 | expect(model.find(1).name).toBe('Joker') 88 | }) 89 | 90 | test('extend an entry by id', () => { 91 | model.update(1, { cape: true }) 92 | expect(model.find(1)).toEqual( 93 | expect.objectContaining({ 94 | name: 'Batman', 95 | cape: true 96 | }) 97 | ) 98 | }) 99 | 100 | test('return false if no entry matches', () => { 101 | expect(model.update(2, {})).toBe(false) 102 | }) 103 | }) 104 | -------------------------------------------------------------------------------- /13-building-a-library--customize/model.js: -------------------------------------------------------------------------------- 1 | export default class Model { 2 | constructor(options = {}) { 3 | const data = options.data || [] 4 | delete options.data 5 | this.$collection = [] 6 | this.$options = Object.assign({ primaryKey: 'id' }, options) 7 | 8 | if (data.length) this.record(data) 9 | } 10 | 11 | record(data) { 12 | const mappedData = data.map(entry => { 13 | if (entry[this.$options.primaryKey]) return entry 14 | entry[this.$options.primaryKey] = Date.now() 15 | return entry 16 | }) 17 | this.$collection.push(...mappedData) 18 | } 19 | all() { 20 | return this.$collection.map(entry => Object.assign({}, entry)) 21 | } 22 | 23 | update(key, data) { 24 | const index = this.$collection.findIndex( 25 | entry => entry[this.$options.primaryKey] === key 26 | ) 27 | if (index < 0) return false 28 | this.$collection.splice( 29 | index, 30 | 1, 31 | Object.assign(this.$collection[index], data) 32 | ) 33 | } 34 | 35 | remove(key) { 36 | const index = this.$collection.findIndex( 37 | entry => entry[this.$options.primaryKey] === key 38 | ) 39 | if (index >= 0) this.$collection.splice(index, 1) 40 | } 41 | 42 | find(key) { 43 | const entry = this.$collection.find( 44 | entry => entry[this.$options.primaryKey] === key 45 | ) 46 | 47 | return entry ? Object.assign({}, entry) : null 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /13-building-a-library--customize/model.spec.js: -------------------------------------------------------------------------------- 1 | import Model from './model' 2 | 3 | function createModel(data = [], options = {}) { 4 | return new Model({ 5 | ...options, 6 | data 7 | }) 8 | } 9 | 10 | test('new works', () => { 11 | expect(createModel()).toBeInstanceOf(Model) 12 | }) 13 | 14 | test('model structure', () => { 15 | expect(createModel()).toEqual( 16 | expect.objectContaining({ 17 | $collection: expect.any(Array), 18 | $options: expect.objectContaining({ 19 | primaryKey: 'id' 20 | }), 21 | record: expect.any(Function), 22 | all: expect.any(Function), 23 | find: expect.any(Function), 24 | update: expect.any(Function) 25 | }) 26 | ) 27 | }) 28 | 29 | describe('customizations', () => { 30 | test('we can customize the primaryKey', () => { 31 | const model = createModel([], { 32 | primaryKey: 'name' 33 | }) 34 | expect(model.$options.primaryKey).toBe('name') 35 | }) 36 | }) 37 | 38 | describe('record', () => { 39 | const heroes = [{ id: 1, name: 'Batman' }, { name: 'Black Panther' }] 40 | 41 | test('can add data to the collection', () => { 42 | const model = createModel() 43 | model.record(heroes) 44 | expect(model.$collection).toEqual([ 45 | heroes[0], 46 | { 47 | id: expect.any(Number), 48 | name: heroes[1].name 49 | } 50 | ]) 51 | }) 52 | 53 | test('gets called when data is passed to Model', () => { 54 | const spy = jest.spyOn(Model.prototype, 'record') 55 | const model = createModel(heroes) 56 | expect(spy).toHaveBeenCalled() 57 | spy.mockRestore() 58 | }) 59 | }) 60 | 61 | describe('all', () => { 62 | test('returns empty model', () => { 63 | const model = createModel() 64 | expect(model.all()).toEqual([]) 65 | }) 66 | 67 | test('returns model data', () => { 68 | const model = createModel([{ name: 'Batman' }, { name: 'Joker' }]) 69 | expect(model.all().length).toBe(2) 70 | }) 71 | 72 | test('original data stays intact', () => { 73 | const model = createModel([{ name: 'Batman' }]) 74 | const data = model.all() 75 | data[0].name = 'Joker' 76 | 77 | expect(model.$collection[0].name).toBe('Batman') 78 | }) 79 | }) 80 | 81 | describe('find', () => { 82 | const heroes = [{ id: 1, name: 'Batman' }, { name: 'Black Panther' }] 83 | 84 | test('returns null if nothing matches', () => { 85 | const model = createModel() 86 | expect(model.find(1)).toEqual(null) 87 | }) 88 | 89 | test('find returns a matching entry', () => { 90 | const model = createModel(heroes) 91 | expect(model.find(1)).toEqual(heroes[0]) 92 | }) 93 | }) 94 | 95 | describe('remove', () => { 96 | const heroes = [{ id: 1, name: 'Batman' }, { name: 'Black Panther' }] 97 | 98 | let model 99 | 100 | beforeEach(() => { 101 | const dataset = JSON.parse(JSON.stringify(heroes)) 102 | model = createModel(dataset) 103 | }) 104 | 105 | test('will remove object with given primaryKey', () => { 106 | model.remove(1) 107 | expect(model.$collection.length).toBe(1) 108 | }) 109 | }) 110 | 111 | describe('update', () => { 112 | const heroesAndVillains = [{ id: 1, name: 'Batman' }] 113 | let model 114 | 115 | beforeEach(() => { 116 | const dataset = JSON.parse(JSON.stringify(heroesAndVillains)) 117 | model = createModel(dataset) 118 | }) 119 | 120 | test('an entry by id', () => { 121 | model.update(1, { name: 'Joker' }) 122 | expect(model.find(1).name).toBe('Joker') 123 | }) 124 | 125 | test('extend an entry by id', () => { 126 | model.update(1, { cape: true }) 127 | expect(model.find(1)).toEqual( 128 | expect.objectContaining({ 129 | name: 'Batman', 130 | cape: true 131 | }) 132 | ) 133 | }) 134 | 135 | test('return false if no entry matches', () => { 136 | expect(model.update(2, {})).toBe(false) 137 | }) 138 | }) 139 | -------------------------------------------------------------------------------- /2-running-our-first-test/user.js: -------------------------------------------------------------------------------- 1 | export default class User { 2 | constructor(details) { 3 | const { firstname, lastname } = details 4 | this.firstname = firstname 5 | this.lastname = lastname 6 | } 7 | get name() { 8 | return `${this.firstname} ${this.lastname}` 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /2-running-our-first-test/user.spec.js: -------------------------------------------------------------------------------- 1 | import User from './user' 2 | 3 | describe('User', () => { 4 | test('name returns full name', () => { 5 | const user = new User({ firstname: 'Jane', lastname: 'Doe' }) 6 | expect(user.name).toBe('Jane Doe') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /3-expectations/expectations.spec.js: -------------------------------------------------------------------------------- 1 | describe('expectations', () => { 2 | // Use .toBe for simple comparisons 3 | expect('Some String').toBe('Some String') 4 | expect(13).toBe(13) 5 | expect(1+2).toBe(3) 6 | 7 | // Use .toEqual when comparing complex types 8 | expect({ type: 'array' }).toEqual({ type: 'array'}) 9 | expect([13]).toEqual([13]) 10 | expect([...[1,2,3]]).toEqual([1,2,3]) 11 | 12 | // Use property Matchers if the final value is unknown 13 | const result = { 14 | value: Date.now() // A random Number 15 | } 16 | 17 | expect(result).toEqual({ 18 | value: expect.any(Number) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /4-snapshot-testing/snapshots.spec.js: -------------------------------------------------------------------------------- 1 | const user = { 2 | name: 'Tony Tinkertons', 3 | age: 42, 4 | job: 'inventor' 5 | } 6 | 7 | test('without snapshots', () => { 8 | // Let's add the string version of the user Object 9 | const userString = "{\"name\":\"Tony Tinkerton\",\"age\":42,\"job\":\"inventor\"}" 10 | 11 | // and compare it accordingly 12 | expect(JSON.stringify(user)).toBe(userString) 13 | }) 14 | 15 | test('user matches with snapshot', () => { 16 | expect(user).toMatchSnapshot() 17 | }) 18 | -------------------------------------------------------------------------------- /5-execute-code-before-and-after/movies.js: -------------------------------------------------------------------------------- 1 | export default { 2 | add(collection, movie) { 3 | collection.push({ 4 | title: movie, 5 | rate: null 6 | }) 7 | }, 8 | 9 | rate(obj, rating) { 10 | obj.rate = rating 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /5-execute-code-before-and-after/movies.spec.js: -------------------------------------------------------------------------------- 1 | 2 | import movies from './movies' 3 | 4 | describe('Favorite Movies', () => { 5 | let myMovies = [] 6 | beforeEach(() => { 7 | myMovies = [{ 8 | title: 'Age of Ultron', 9 | rate: null 10 | }] 11 | }) 12 | 13 | test('can add a movie', () => { 14 | movies.add(myMovies, 'Kung Fury') 15 | expect(myMovies).toMatchSnapshot() 16 | }) 17 | 18 | test('rate a movie', () => { 19 | movies.rate(myMovies[0], 5) 20 | expect(myMovies).toMatchSnapshot() 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /8-building-a-library/model.js: -------------------------------------------------------------------------------- 1 | export default class Model { 2 | 3 | constructor() { 4 | this.$collection = [] 5 | } 6 | 7 | record() { } 8 | all() { } 9 | update() { } 10 | find() { } 11 | } 12 | -------------------------------------------------------------------------------- /8-building-a-library/model.spec.js: -------------------------------------------------------------------------------- 1 | import Model from './model' 2 | 3 | test('new works', () => { 4 | expect(new Model).toBeInstanceOf(Model) 5 | }) 6 | 7 | test('model structure', () => { 8 | expect(new Model).toEqual(expect.objectContaining({ 9 | $collection: expect.any(Array), 10 | record: expect.any(Function), 11 | all: expect.any(Function), 12 | find: expect.any(Function), 13 | update: expect.any(Function), 14 | })) 15 | }) 16 | -------------------------------------------------------------------------------- /9-building-a-library--record/model.js: -------------------------------------------------------------------------------- 1 | export default class Model { 2 | 3 | constructor(data = []) { 4 | this.$collection = [] 5 | 6 | if(data.length) 7 | this.record(data) 8 | } 9 | 10 | record(data) { 11 | this.$collection.push(...data) 12 | } 13 | all() {} 14 | update() {} 15 | find() {} 16 | } 17 | -------------------------------------------------------------------------------- /9-building-a-library--record/model.spec.js: -------------------------------------------------------------------------------- 1 | import Model from './model' 2 | 3 | test('new works', () => { 4 | expect(new Model).toBeInstanceOf(Model) 5 | }) 6 | 7 | test('model structure', () => { 8 | expect(new Model).toEqual(expect.objectContaining({ 9 | $collection: expect.any(Array), 10 | record: expect.any(Function), 11 | all: expect.any(Function), 12 | find: expect.any(Function), 13 | update: expect.any(Function), 14 | })) 15 | }) 16 | 17 | describe('record', () => { 18 | const heroes = [{name: 'Batman'}, { name: 'Black Panther'}] 19 | 20 | test('can add data to the collection', () => { 21 | const model = new Model() 22 | model.record(heroes) 23 | expect(model.$collection).toEqual(heroes) 24 | }) 25 | 26 | test('gets called when data is passed to Model', () => { 27 | const spy = jest.spyOn(Model.prototype, 'record') 28 | const model = new Model(heroes) 29 | expect(spy).toHaveBeenCalled() 30 | spy.mockRestore() 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unit-testing-jest 2 | 🗂 Source code for Unit Testing with Jest 3 | 4 | # How to use this Repo 5 | 6 | If you want to run all the tests of this repository, 7 | copy the `.babelrc` and `package.json` file from the `1-installing-jest` folder 8 | into the root folder. 9 | 10 | Then add `jest` to the project: 11 | 12 | ``` 13 | yarn add jest && yarn install 14 | ``` 15 | 16 | Then you are good to run `jest` directly. 17 | 18 | ``` 19 | yarn test 20 | # or 21 | yarn jest 22 | ``` 23 | --------------------------------------------------------------------------------