The testing world is full of ways to simulate data and user interaction. Mocks, spies, stubs, and timers all allow you to focus your tests and remove unwanted interference. Maybe you've tried to set them up and been overwhelmed or maybe you've never even heard of them. Core concepts can help developers frame their thoughts about testing and structuring tests. We'll explore those core concepts and look at how to leverage existing libraries like Sinon.js, TestDouble.js, and even Jest, with examples to make your test suite more stable and reliable.
7. Unit Tests
Unit Tests are useful for testing pure functions
where the return value is only determined by its
input values, without any observable side
effects.
90. Usage: SinonJS
Spies When you want to verify a callback or
ensure a function was called with specific
inputs.
91. Usage: SinonJS
Spies When you want to verify a callback or
ensure a function was called with specific
inputs.
Stubs When you need to simulate different
responses from systems that are complicated
to initialize.
92. Usage: SinonJS
Spies When you want to verify a callback or
ensure a function was called with specific
inputs.
Stubs When you need to simulate different
responses from systems that are complicated
to initialize.
Fakes When you can't remember whether to use a
spy or a stub.
93. Usage: SinonJS
Spies When you want to verify a callback or
ensure a function was called with specific
inputs.
Stubs When you need to simulate different
responses from systems that are complicated
to initialize.
Fakes When you can't remember whether to use a
spy or a stub.
Mocks If you like to set up your assertions at the
beginning when you define your mocked
behavior.
114. TestDouble.js
• Combines all into single test double function
• Allows for dynamic object stubbing
• Simple replace functions
115. TestDouble.js
• Combines all into single test double function
• Allows for dynamic object stubbing
• Simple replace functions
• Always replaces functions rather than wrapping
116. TestDouble.js
• Combines all into single test double function
• Allows for dynamic object stubbing
• Simple replace functions
• Always replaces functions rather than wrapping
• Nice introspection
117. TestDouble.js
• Combines all into single test double function
• Allows for dynamic object stubbing
• Simple replace functions
• Always replaces functions rather than wrapping
• Nice introspection
• ember-cli-testdouble (-chai, -qunit)
122. Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
123. Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
• Allows manual mocks
124. Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
• Allows manual mocks
• Snapshot tests
125. Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
• Allows manual mocks
• Snapshot tests
• Can include test coverage
126. Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
• Allows manual mocks
• Snapshot tests
• Can include test coverage
• Does not run in browser
131. Mocking makes refactoring harder
"the process of changing a software system in such a
way that it does not alter the external behavior of the
code yet improves its internal structure"
Martin Fowler
132. Mocking is a Code Smell
"A code smell is a surface indication that usually
corresponds to a deeper problem in the system."
Martin Fowler
133. Mocking violates DRY
"Every piece of knowledge must have a single,
unambiguous, authoritative representation within a
system."
The Pragmatic Programmer
137. Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
138. Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
139. Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
Tests
140. Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
Tests
Service Mock
141. Don't mock what you don't own
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
if (isMagicShoes(shoes) && clicked === 3) {
wishCallback();
}
}
}
let clicked = await this.ajax.request('https://www.random.org/integers/?
num=1&min=1&max=5&col=1&base=10&format=plain&rnd=new');
142. Don't mock what you don't own
export default class HeelClick extends Service {
ajax: service(),
async getClickCount() {
}
}
return this.ajax.request('https://www.random.org/integers/?
num=1&min=1&max=5&col=1&base=10&format=plain&rnd=new');
143. Don't mock what you don't own
export default class HeelClick extends Service {
ajax: service(),
async getClickCount() {
}
}
return this.ajax.request('https://www.random.org/integers/?
num=1&min=1&max=5&col=1&base=10&format=plain&rnd=new');
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
if (isMagicShoes(shoes) && clicked === 3) {
wishCallback();
}
}
}
let clicked = await this.ajax.request('https://www.random.org/integers/?
num=1&min=1&max=5&col=1&base=10&format=plain&rnd=new');
144. Don't mock what you don't own
export default class HeelClick extends Service {
ajax: service(),
async getClickCount() {
}
}
return this.ajax.request('https://www.random.org/integers/?
num=1&min=1&max=5&col=1&base=10&format=plain&rnd=new');
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
if (isMagicShoes(shoes) && clicked === 3) {
wishCallback();
}
}
}
let clicked = await this.heelClick.getClickedCount();
heelClick: service(),
145. Don't mock what you don't own
test('Dorothy is happy when magic happens', async function(assert) {
});
const magicService = this.owner.lookup('service:magic');
const stub = sinon.stub(magicService.ajax, 'request').returns(3);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
assert.notOk(this.element.querySelector('.happy'));
const shoes = this.element.querySelector('.shoes');
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
assert.ok(stub.calledOnce);
146. Don't mock what you don't own
test('Dorothy is happy when magic happens', async function(assert) {
});
const heelClick = this.owner.lookup('service:heelClick');
const stub = sinon.stub(heelClick, 'getClickedCount').returns(3);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
assert.notOk(this.element.querySelector('.happy'));
const shoes = this.element.querySelector('.shoes');
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
assert.ok(stub.calledOnce);
147. Don't strive for 100% coverage
"I expect a high level of coverage. Sometimes managers
require one. There's a subtle difference."
Brian Marick
149. Test behavior, not implementation
"Implementation details are things which users of your
code will not typically use, see, or event know about."
Kent C. Dodds