SlideShare a Scribd company logo
1 of 150
Download to read offline
Mocks, Spies, and Timers
Oh My!
Lisa Backer
Senior Software Engineer
DockYard
Ambitious Applications
Ambitious Tests
Thoughtful Tests
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.
Unit Tests
export const isMagicShoes = (shoes) => {
}
Unit Tests
export const isMagicShoes = (shoes) => {
}
return shoes !== undefined && shoes.type === 'ruby';
Unit Tests
module('Unit | Utils | shoes', function() {
module('isMagicShoes', function() {
test('returns true only for magic shoes', function(assert) {
});
});
});
export const isMagicShoes = (shoes) => {
}
return shoes !== undefined && shoes.type === 'ruby';
Unit Tests
module('Unit | Utils | shoes', function() {
module('isMagicShoes', function() {
test('returns true only for magic shoes', function(assert) {
});
});
});
assert.deepEqual(isMagicShoes({ type: 'ruby' }), true);
export const isMagicShoes = (shoes) => {
}
return shoes !== undefined && shoes.type === 'ruby';
Unit Tests
module('Unit | Utils | shoes', function() {
module('isMagicShoes', function() {
test('returns true only for magic shoes', function(assert) {
});
});
});
assert.deepEqual(isMagicShoes({ type: 'ruby' }), true);
assert.deepEqual(isMagicShoes({ type: 'silver' }), false);
export const isMagicShoes = (shoes) => {
}
return shoes !== undefined && shoes.type === 'ruby';
Unit Tests
module('Unit | Utils | shoes', function() {
module('isMagicShoes', function() {
test('returns true only for magic shoes', function(assert) {
});
});
});
assert.deepEqual(isMagicShoes({ type: 'ruby' }), true);
assert.deepEqual(isMagicShoes({ type: 'silver' }), false);
assert.deepEqual(isMagicShoes(), false);
export const isMagicShoes = (shoes) => {
}
return shoes !== undefined && shoes.type === 'ruby';
What Are We Testing?
Container Tests
Test the functionality around an Ember class
instance by setting up the application's container.
Container Tests
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
}
}
Container Tests
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
}
}
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
Container Tests
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
}
}
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
module('Unit | Service | magic', function(hooks) {
setupTest(hooks);
test('makeWish', function(assert) {
});
});
Container Tests
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
}
}
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
module('Unit | Service | magic', function(hooks) {
setupTest(hooks);
test('makeWish', function(assert) {
});
});
let service = this.owner.lookup('service:magic');
let wish = () => {
assert.ok(true, 'Wish was made');
}
Container Tests
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
}
}
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
module('Unit | Service | magic', function(hooks) {
setupTest(hooks);
test('makeWish', function(assert) {
});
});
let service = this.owner.lookup('service:magic');
let wish = () => {
assert.ok(true, 'Wish was made');
}
service.makeWish({ type: 'ruby', clicked: 3 }, wish);
Container Tests
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
}
}
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
module('Unit | Service | magic', function(hooks) {
setupTest(hooks);
test('makeWish', function(assert) {
});
});
let service = this.owner.lookup('service:magic');
let wish = () => {
assert.ok(true, 'Wish was made');
}
assert.expect(1);
service.makeWish({ type: 'ruby', clicked: 3 }, wish);
What Are We Testing?
Rendering Tests
Test the interactions between various parts of
the application, such as behavior between UI
controls.
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
Rendering Tests
<button class="shoes" {{on "click" this.theresNoPlaceLikeHome}}>
Shoes
</button>
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
onMakeWish() => {}
actions: {
theresNoPlaceLikeHome() {
}
}
});
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
onMakeWish() => {}
actions: {
theresNoPlaceLikeHome() {
}
}
});
set(this, 'shoes.clicked', this.shoes.clicked + 1);
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
onMakeWish() => {}
actions: {
theresNoPlaceLikeHome() {
}
}
});
set(this, 'shoes.clicked', this.shoes.clicked + 1);
this.magic.makeWish(this.shoes, () => {
});
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
onMakeWish() => {}
actions: {
theresNoPlaceLikeHome() {
}
}
});
set(this, 'shoes.clicked', this.shoes.clicked + 1);
this.magic.makeWish(this.shoes, () => {
});
set(this, 'isHappy', true);
this.onMakeWish();
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
actions: {
theresNoPlaceLikeHome() {
}
}
});
set(this, 'shoes.clicked', this.shoes.clicked + 1);
this.magic.makeWish(this.shoes, () => {
set(this, 'isHappy', true);
});
test('Dorothy is happy when magic happens', async function(assert) {
this.set('shoes', { type: 'ruby', clicked: 0 });
});
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
actions: {
theresNoPlaceLikeHome() {
}
}
});
set(this, 'shoes.clicked', this.shoes.clicked + 1);
this.magic.makeWish(this.shoes, () => {
set(this, 'isHappy', true);
});
test('Dorothy is happy when magic happens', async function(assert) {
this.set('shoes', { type: 'ruby', clicked: 0 });
});
await render(hbs`<Dorothy @shoes={{shoes}} />`);
assert.notOk(this.element.querySelector('.happy'));
assert.ok(this.element.querySelector('.scared'));
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
actions: {
theresNoPlaceLikeHome() {
}
}
});
set(this, 'shoes.clicked', this.shoes.clicked + 1);
this.magic.makeWish(this.shoes, () => {
set(this, 'isHappy', true);
});
test('Dorothy is happy when magic happens', async function(assert) {
this.set('shoes', { type: 'ruby', clicked: 0 });
});
await render(hbs`<Dorothy @shoes={{shoes}} />`);
assert.notOk(this.element.querySelector('.happy'));
assert.ok(this.element.querySelector('.scared'));
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
Rendering Tests
{{#if isHappy}}
<p class="happy">Dorothy is Happy</p>
{{else}}
<p class="scared">Dorothy is scared and wants to go home</p>
{{/if}}
<button class="shoes" {{action "theresNoPlaceLikeHome"}}>
Clicked {{this.shoes.clicked}}
</button>
export default Component.extend({
magic: inject(),
isHappy: false,
actions: {
theresNoPlaceLikeHome() {
}
}
});
set(this, 'shoes.clicked', this.shoes.clicked + 1);
this.magic.makeWish(this.shoes, () => {
set(this, 'isHappy', true);
});
test('Dorothy is happy when magic happens', async function(assert) {
this.set('shoes', { type: 'ruby', clicked: 0 });
});
await render(hbs`<Dorothy @shoes={{shoes}} />`);
assert.notOk(this.element.querySelector('.happy'));
assert.ok(this.element.querySelector('.scared'));
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
assert.notOk(this.element.querySelector('.scared'));
What Are We Testing?
Application Tests
Test user interaction and application flow in
order to verify user stories or a feature from an
end-user perspective.
Application Tests
<h1>Oz</h1>
{{dorothy shoes=model.shoes onMakeWish=(action "goHome")}}
Application Tests
<h1>Oz</h1>
{{dorothy shoes=model.shoes onMakeWish=(action "goHome")}}
export default Controller.extend({
  actions: {
    goHome() {
      this.transitionToRoute('kansas');
    }
  }
});
Application Tests
test('Make a wish', async function(assert) {
});
Application Tests
test('Make a wish', async function(assert) {
});
this.owner.lookup('route:oz').model = function() {
return {
shoes: {
type: 'ruby',
clicked: 0
}
};
};
Application Tests
test('Make a wish', async function(assert) {
});
this.owner.lookup('route:oz').model = function() {
return {
shoes: {
type: 'ruby',
clicked: 0
}
};
};
await visit('/oz');
assert.equal(currentURL(), '/oz');
Application Tests
test('Make a wish', async function(assert) {
});
this.owner.lookup('route:oz').model = function() {
return {
shoes: {
type: 'ruby',
clicked: 0
}
};
};
await visit('/oz');
assert.equal(currentURL(), '/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
Application Tests
test('Make a wish', async function(assert) {
});
this.owner.lookup('route:oz').model = function() {
return {
shoes: {
type: 'ruby',
clicked: 0
}
};
};
await visit('/oz');
assert.equal(currentURL(), '/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
assert.equal(currentURL(), '/kansas');
What Are We Testing?
Unit Tests
export const isMagicShoes = (shoes) => {
return shoes !== undefined && shoes.type === 'ruby';
}
Unit Tests
export const isMagicShoes = (shoes) => {
return shoes !== undefined && shoes.type === 'silver';
}
Unit Tests
module('Unit | Utils | shoes', function() {
module('isMagicShoes', function() {
test('returns true only for magic shoes', function(assert) {
assert.deepEqual(isMagicShoes({ type: 'ruby' }), true);
assert.deepEqual(isMagicShoes({ type: 'silver' }), false);
assert.deepEqual(isMagicShoes(), false);
});
});
});
Unit Tests
module('Unit | Utils | shoes', function() {
module('isMagicShoes', function() {
test('returns true only for magic shoes', function(assert) {
assert.deepEqual(isMagicShoes({ type: 'ruby' }), false);
assert.deepEqual(isMagicShoes({ type: 'silver' }), true);
assert.deepEqual(isMagicShoes(), false);
});
});
});
Container Tests
module('Unit | Service | magic', function(hooks) {
setupTest(hooks);
test('makeWish', function(assert) {
assert.expect(1);
let service = this.owner.lookup('service:magic');
let wish = () => {
assert.ok(true, 'Wish was made');
}
service.makeWish({ type: 'ruby', clicked: 3 }, wish);
});
});
Container Tests
module('Unit | Service | magic', function(hooks) {
setupTest(hooks);
test('makeWish', function(assert) {
assert.expect(1);
let service = this.owner.lookup('service:magic');
let wish = () => {
assert.ok(true, 'Wish was made');
}
service.makeWish({ type: 'silver', clicked: 3 }, wish);
});
});
Rendering Tests
test('Dorothy is happy when magic happens', async function(assert) {
this.set('shoes', { type: 'ruby', clicked: 0 });
await render(hbs`<Dorothy @shoes={{shoes}} />`);
assert.notOk(this.element.querySelector('.happy'));
assert.ok(this.element.querySelector('.scared'));
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
assert.notOk(this.element.querySelector('.scared'));
});
Rendering Tests
test('Dorothy is happy when magic happens', async function(assert) {
this.set('shoes', { type: 'silver', clicked: 0 });
await render(hbs`<Dorothy @shoes={{shoes}} />`);
assert.notOk(this.element.querySelector('.happy'));
assert.ok(this.element.querySelector('.scared'));
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
assert.notOk(this.element.querySelector('.scared'));
});
Application Tests
test('Make a wish', async function(assert) {
this.owner.lookup('route:oz').model = function() {
return {
shoes: {
type: 'ruby',
clicked: 0
}
};
};
await visit('/oz');
assert.equal(currentURL(), '/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
assert.equal(currentURL(), '/kansas');
});
Application Tests
test('Make a wish', async function(assert) {
this.owner.lookup('route:oz').model = function() {
return {
shoes: {
type: 'silver',
clicked: 0
}
};
};
await visit('/oz');
assert.equal(currentURL(), '/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
assert.equal(currentURL(), '/kansas');
});
What Are We Testing?
System Under Test
"Short for 'whatever we are testing' and is always
defined from the perspective of the test."
xUnit Patterns
Usage: SinonJS
Usage: SinonJS
SinonJS: Spies
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
test('makeWish', function(assert) {
});
let service = this.owner.lookup('service:magic');
let wish = () => {
assert.ok(true, 'Wish was made');
}
assert.expect(1);
service.makeWish(myShoes, wish);
SinonJS: Spies
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
SinonJS: Spies
test('makeWish', function(assert) {
});
    let wish = sinon.spy();
    let service = this.owner.lookup('service:magic');
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
SinonJS: Spies
test('makeWish', function(assert) {
});
    service.makeWish(myShoes, wish);
    let wish = sinon.spy();
    let service = this.owner.lookup('service:magic');
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
SinonJS: Spies
test('makeWish', function(assert) {
});
    service.makeWish(myShoes, wish);
    let wish = sinon.spy();
    let service = this.owner.lookup('service:magic');
    assert.ok(wish.calledOnce);
import * as ShoeUtils from 'oz/utils/shoes';
test('shoes are validated with each heel click',
async function(assert) {
});
SinonJS: Spies
import * as ShoeUtils from 'oz/utils/shoes';
test('shoes are validated with each heel click',
async function(assert) {
});
SinonJS: Spies
const spy = sinon.spy(ShoeUtils, 'isMagicShoes');
await render(hbs`<Dorothy @shoes={{shoes}} />`);
import * as ShoeUtils from 'oz/utils/shoes';
test('shoes are validated with each heel click',
async function(assert) {
});
SinonJS: Spies
const spy = sinon.spy(ShoeUtils, 'isMagicShoes');
await render(hbs`<Dorothy @shoes={{shoes}} />`);
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
import * as ShoeUtils from 'oz/utils/shoes';
test('shoes are validated with each heel click',
async function(assert) {
});
SinonJS: Spies
const spy = sinon.spy(ShoeUtils, 'isMagicShoes');
await render(hbs`<Dorothy @shoes={{shoes}} />`);
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
assert.equal(spy.callCount, 3);
SinonJS: Stubs
SinonJS: Stubs
export default class Magic extends Service {
makeWish(shoes, wishCallback) {
if (isMagicShoes(shoes) && clicked === 3) {
wishCallback();
}
}
}
let clicked = shoes.clicked;
SinonJS: Stubs
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');
SinonJS: Stubs
test('Dorothy is happy when magic happens', async function(assert) {
});
SinonJS: Stubs
test('Dorothy is happy when magic happens', async function(assert) {
});
const magicService = this.owner.lookup('service:magic');
const stub = sinon.stub(magicService.ajax, 'request')
.onCall(0).resolves(1)
.onCall(1).resolves(3);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
SinonJS: Stubs
test('Dorothy is happy when magic happens', async function(assert) {
});
const shoes = this.element.querySelector('.shoes');
await click(shoes);
assert.notOk(this.element.querySelector('.happy'));
const magicService = this.owner.lookup('service:magic');
const stub = sinon.stub(magicService.ajax, 'request')
.onCall(0).resolves(1)
.onCall(1).resolves(3);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
SinonJS: Stubs
test('Dorothy is happy when magic happens', async function(assert) {
});
const shoes = this.element.querySelector('.shoes');
await click(shoes);
assert.notOk(this.element.querySelector('.happy'));
const magicService = this.owner.lookup('service:magic');
const stub = sinon.stub(magicService.ajax, 'request')
.onCall(0).resolves(1)
.onCall(1).resolves(3);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
await click(shoes);
SinonJS: Stubs
test('Dorothy is happy when magic happens', async function(assert) {
});
const shoes = this.element.querySelector('.shoes');
await click(shoes);
assert.notOk(this.element.querySelector('.happy'));
const magicService = this.owner.lookup('service:magic');
const stub = sinon.stub(magicService.ajax, 'request')
.onCall(0).resolves(1)
.onCall(1).resolves(3);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
assert.equal(stub.callCount.length, 2);
SinonJS: Fakes
SinonJS: Fakes
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
test('makeWish', function(assert) {
});
    service.makeWish({ type: 'ruby', clicked: 3 }, wish);
    let wish = sinon.spy();
    assert.ok(spy.calledOnce);
    let service = this.owner.lookup('service:magic');
SinonJS: Fakes
if (isMagicShoes(shoes) && shoes.clicked === 3) {
wishCallback();
}
test('makeWish', function(assert) {
});
    service.makeWish({ type: 'ruby', clicked: 3 }, wish);
    let wish = sinon.fake();
    assert.ok(spy.calledOnce);
    let service = this.owner.lookup('service:magic');
test('no magic wishes for non-magic shoes', async function(assert) {
});
const spy = sinon.spy(ShoeUtils, 'isMagicShoes');
SinonJS: Fakes
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
assert.ok(spy.calledOnce);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
test('no magic wishes for non-magic shoes', async function(assert) {
});
SinonJS: Fakes
const fake = sinon.fake.returns(false);
sinon.replace(ShoeUtils, 'isMagicShoes', fake);
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
assert.equal(fake.callCount, 3);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
SinonJS: Mocks
SinonJS: Mocks
test('clicking three times with magic shoes', async function(assert) {
});
SinonJS: Mocks
test('clicking three times with magic shoes', async function(assert) {
});
const mock = sinon.mock(ShoeUtils);
mock.expects('isMagicShoes').exactly(3).returns(true);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
SinonJS: Mocks
test('clicking three times with magic shoes', async function(assert) {
});
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
const mock = sinon.mock(ShoeUtils);
mock.expects('isMagicShoes').exactly(3).returns(true);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
SinonJS: Mocks
test('clicking three times with magic shoes', async function(assert) {
});
const shoes = this.element.querySelector('.shoes');
await click(shoes);
await click(shoes);
await click(shoes);
assert.ok(this.element.querySelector('.happy'));
mock.verify();
const mock = sinon.mock(ShoeUtils);
mock.expects('isMagicShoes').exactly(3).returns(true);
await render(hbs`<Dorothy @shoes={{shoes}} />`);
Usage: SinonJS
Usage: SinonJS
Spies When you want to verify a callback or
ensure a function was called with specific
inputs.
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.
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.
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.
SinonJS: Timers
SinonJS: Timers
setTimeout
clearTimeout
setImmediate
clearImmediate
process.hrtime
performance.now
Date
SinonJS: Timers
actions: {
goHome() {
this.transitionToRoute('kansas');
}
}
SinonJS: Timers
actions: {
goHome() {
this.transitionToRoute('kansas');
}
}
// ooohhh suspense
setTimeout(() => {
}, 5000);
SinonJS: Timers
actions: {
goHome() {
// ooohhh suspense
setTimeout(() => {
this.transitionToRoute('kansas');
}, 5000);
}
}
test('Make a wish', async function(assert) {
  await visit('/oz');
  assert.equal(currentURL(), '/oz');
  await click('.shoes');
  await click('.shoes');
  await click('.shoes');
  assert.equal(currentURL(), '/oz');
});
assert.equal(currentURL(), '/kansas');
// ¯_(ツ)_/¯
SinonJS: Timers
actions: {
goHome() {
// ooohhh suspense
setTimeout(() => {
this.transitionToRoute('kansas');
}, 5000);
}
}
test('Make a wish', async function(assert) {
const clock = sinon.useFakeTimers();
});
SinonJS: Timers
actions: {
goHome() {
// ooohhh suspense
setTimeout(() => {
this.transitionToRoute('kansas');
}, 5000);
}
}
test('Make a wish', async function(assert) {
const clock = sinon.useFakeTimers();
});
await visit('/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
SinonJS: Timers
actions: {
goHome() {
// ooohhh suspense
setTimeout(() => {
this.transitionToRoute('kansas');
}, 5000);
}
}
test('Make a wish', async function(assert) {
const clock = sinon.useFakeTimers();
});
await visit('/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
assert.equal(currentURL(), '/oz');
SinonJS: Timers
actions: {
goHome() {
// ooohhh suspense
setTimeout(() => {
this.transitionToRoute('kansas');
}, 5000);
}
}
test('Make a wish', async function(assert) {
const clock = sinon.useFakeTimers();
});
await visit('/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
assert.equal(currentURL(), '/oz');
clock.runAll();
await settled();
SinonJS: Timers
actions: {
goHome() {
// ooohhh suspense
setTimeout(() => {
this.transitionToRoute('kansas');
}, 5000);
}
}
test('Make a wish', async function(assert) {
const clock = sinon.useFakeTimers();
});
await visit('/oz');
await click('.shoes');
await click('.shoes');
await click('.shoes');
assert.equal(currentURL(), '/oz');
clock.runAll();
await settled();
assert.equal(currentURL(), '/kansas');
Usage: SinonJS
ember-sinon
ember-sinon-chai
Usage: SinonJS
ember-sinon
ember-sinon-qunit
ember-cli-chai
Usage: SinonJS
ember-sinon
ember-sinon-qunit
ember-cli-chai
Usage: SinonJS
ember-sinon
ember-sinon-qunit
ember-sinon-sandbox
ember-cli-chai
Usage: SinonJS
ember-sinon
ember-sinon-qunit
ember-sinon-sinoff
ember-cli-chai
Usage: SinonJS
ember-sinon
ember-sinon-qunit
TestDouble.js
TestDouble.js
TestDouble.js
• Combines all into single test double function
TestDouble.js
• Combines all into single test double function
• Allows for dynamic object stubbing
TestDouble.js
• Combines all into single test double function
• Allows for dynamic object stubbing
• Simple replace functions
TestDouble.js
• Combines all into single test double function
• Allows for dynamic object stubbing
• Simple replace functions
• Always replaces functions rather than wrapping
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
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)
Jest
Jest
Jest
Jest
• Full set of assertions using a chai-like syntax
Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
• Allows manual mocks
Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
• Allows manual mocks
• Snapshot tests
Jest
• Full set of assertions using a chai-like syntax
• Allows automatic mocking
• Allows manual mocks
• Snapshot tests
• Can include test coverage
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
Mocking makes
refactoring harder
Mocking makes
refactoring harder
Mocking is a Code Smell
Mocking makes
refactoring harder
Mocking is a Code Smell
Mocking violates DRY
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
Mocking is a Code Smell
"A code smell is a surface indication that usually
corresponds to a deeper problem in the system."
Martin Fowler
Mocking violates DRY
"Every piece of knowledge must have a single,
unambiguous, authoritative representation within a
system."
The Pragmatic Programmer
Don't mock what you don't own
API
Data
Files
HTTP
Don't mock what you don't own
API
Data
Files
HTTPService
Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
Tests
Don't mock what you don't own
API
Data
Files
HTTPService
Codebase
Tests
Service Mock
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');
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');
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');
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(),
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);
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);
Don't strive for 100% coverage
"I expect a high level of coverage. Sometimes managers
require one. There's a subtle difference."
Brian Marick
What Are We Testing?
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
Lisa Backer, DockYard
@eshtadc
Thank you!

More Related Content

What's hot

Presentation technico-commercial-ruby-on-rails
Presentation technico-commercial-ruby-on-railsPresentation technico-commercial-ruby-on-rails
Presentation technico-commercial-ruby-on-railsNovelys
 
Web Security - Hands-on
Web Security - Hands-onWeb Security - Hands-on
Web Security - Hands-onAndrea Valenza
 
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native appBeginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native appKaty Slemon
 
PyCon Siberia 2016. Не доверяйте тестам!
PyCon Siberia 2016. Не доверяйте тестам!PyCon Siberia 2016. Не доверяйте тестам!
PyCon Siberia 2016. Не доверяйте тестам!Ivan Tsyganov
 
Opening iOS App 開發者交流會
Opening iOS App 開發者交流會Opening iOS App 開發者交流會
Opening iOS App 開發者交流會Michael Pan
 
Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.Astrails
 
Angular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app exampleAngular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app exampleAlexey Frolov
 
ALPHA Script - XML Model
ALPHA Script - XML ModelALPHA Script - XML Model
ALPHA Script - XML ModelPROBOTEK
 

What's hot (18)

Mpg Dec07 Gian Lorenzetto
Mpg Dec07 Gian Lorenzetto Mpg Dec07 Gian Lorenzetto
Mpg Dec07 Gian Lorenzetto
 
Espresso devoxx 2014
Espresso devoxx 2014Espresso devoxx 2014
Espresso devoxx 2014
 
Presentation technico-commercial-ruby-on-rails
Presentation technico-commercial-ruby-on-railsPresentation technico-commercial-ruby-on-rails
Presentation technico-commercial-ruby-on-rails
 
Web Security - Hands-on
Web Security - Hands-onWeb Security - Hands-on
Web Security - Hands-on
 
HTML Form Part 1
HTML Form Part 1HTML Form Part 1
HTML Form Part 1
 
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native appBeginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
 
PyCon Siberia 2016. Не доверяйте тестам!
PyCon Siberia 2016. Не доверяйте тестам!PyCon Siberia 2016. Не доверяйте тестам!
PyCon Siberia 2016. Не доверяйте тестам!
 
Clean code
Clean codeClean code
Clean code
 
Opening iOS App 開發者交流會
Opening iOS App 開發者交流會Opening iOS App 開發者交流會
Opening iOS App 開發者交流會
 
Team pdf ionic
Team pdf ionicTeam pdf ionic
Team pdf ionic
 
Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.
 
DBMS LAB
DBMS LABDBMS LAB
DBMS LAB
 
Chainable datasource
Chainable datasourceChainable datasource
Chainable datasource
 
Practical
PracticalPractical
Practical
 
Clean Test Code
Clean Test CodeClean Test Code
Clean Test Code
 
Angular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app exampleAngular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app example
 
Os Harris
Os HarrisOs Harris
Os Harris
 
ALPHA Script - XML Model
ALPHA Script - XML ModelALPHA Script - XML Model
ALPHA Script - XML Model
 

Similar to Mocks, Spies, and Timers - Oh My!

ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wildJoe Morgan
 
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfJAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfcalderoncasto9163
 
04 Advanced Javascript
04 Advanced Javascript04 Advanced Javascript
04 Advanced Javascriptcrgwbr
 
Stop Making Excuses and Start Testing Your JavaScript
Stop Making Excuses and Start Testing Your JavaScriptStop Making Excuses and Start Testing Your JavaScript
Stop Making Excuses and Start Testing Your JavaScriptRyan Anklam
 
How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014Guillaume POTIER
 
Modern JavaScript Engine Performance
Modern JavaScript Engine PerformanceModern JavaScript Engine Performance
Modern JavaScript Engine PerformanceCatalin Dumitru
 
A re introduction to webpack - reactfoo - mumbai
A re introduction to webpack - reactfoo - mumbaiA re introduction to webpack - reactfoo - mumbai
A re introduction to webpack - reactfoo - mumbaiPraveen Puglia
 
Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreNicolas Carlo
 
Webmontag Berlin "coffee script"
Webmontag Berlin "coffee script"Webmontag Berlin "coffee script"
Webmontag Berlin "coffee script"Webmontag Berlin
 
Building evented single page applications
Building evented single page applicationsBuilding evented single page applications
Building evented single page applicationsSteve Smith
 
Building Evented Single Page Applications
Building Evented Single Page ApplicationsBuilding Evented Single Page Applications
Building Evented Single Page ApplicationsSteve Smith
 
srcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docx
srcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docxsrcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docx
srcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docxwhitneyleman54422
 
Kotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan SoaresKotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan SoaresiMasters
 
jQuery: out with the old, in with the new
jQuery: out with the old, in with the newjQuery: out with the old, in with the new
jQuery: out with the old, in with the newRemy Sharp
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxMichelangelo van Dam
 
Build Lightweight Web Module
Build Lightweight Web ModuleBuild Lightweight Web Module
Build Lightweight Web ModuleMorgan Cheng
 

Similar to Mocks, Spies, and Timers - Oh My! (20)

ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wild
 
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfJAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
 
04 Advanced Javascript
04 Advanced Javascript04 Advanced Javascript
04 Advanced Javascript
 
Clean Javascript
Clean JavascriptClean Javascript
Clean Javascript
 
Stop Making Excuses and Start Testing Your JavaScript
Stop Making Excuses and Start Testing Your JavaScriptStop Making Excuses and Start Testing Your JavaScript
Stop Making Excuses and Start Testing Your JavaScript
 
Google guava
Google guavaGoogle guava
Google guava
 
How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014
 
Modern JavaScript Engine Performance
Modern JavaScript Engine PerformanceModern JavaScript Engine Performance
Modern JavaScript Engine Performance
 
A re introduction to webpack - reactfoo - mumbai
A re introduction to webpack - reactfoo - mumbaiA re introduction to webpack - reactfoo - mumbai
A re introduction to webpack - reactfoo - mumbai
 
Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscore
 
Webmontag Berlin "coffee script"
Webmontag Berlin "coffee script"Webmontag Berlin "coffee script"
Webmontag Berlin "coffee script"
 
Building evented single page applications
Building evented single page applicationsBuilding evented single page applications
Building evented single page applications
 
Building Evented Single Page Applications
Building Evented Single Page ApplicationsBuilding Evented Single Page Applications
Building Evented Single Page Applications
 
Nativescript angular
Nativescript angularNativescript angular
Nativescript angular
 
srcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docx
srcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docxsrcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docx
srcArtifact.javasrcArtifact.javaclassArtifactextendsCave{pub.docx
 
Kotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan SoaresKotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan Soares
 
jQuery: out with the old, in with the new
jQuery: out with the old, in with the newjQuery: out with the old, in with the new
jQuery: out with the old, in with the new
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
Build Lightweight Web Module
Build Lightweight Web ModuleBuild Lightweight Web Module
Build Lightweight Web Module
 

Recently uploaded

How to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfHow to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfLivetecs LLC
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaHanief Utama
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfFerryKemperman
 
Buds n Tech IT Solutions: Top-Notch Web Services in Noida
Buds n Tech IT Solutions: Top-Notch Web Services in NoidaBuds n Tech IT Solutions: Top-Notch Web Services in Noida
Buds n Tech IT Solutions: Top-Notch Web Services in Noidabntitsolutionsrishis
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....kzayra69
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfStefano Stabellini
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样umasea
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Hr365.us smith
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 

Recently uploaded (20)

How to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfHow to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdf
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief Utama
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdf
 
Buds n Tech IT Solutions: Top-Notch Web Services in Noida
Buds n Tech IT Solutions: Top-Notch Web Services in NoidaBuds n Tech IT Solutions: Top-Notch Web Services in Noida
Buds n Tech IT Solutions: Top-Notch Web Services in Noida
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdf
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
2.pdf Ejercicios de programación competitiva
2.pdf Ejercicios de programación competitiva2.pdf Ejercicios de programación competitiva
2.pdf Ejercicios de programación competitiva
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 

Mocks, Spies, and Timers - Oh My!

  • 1. Mocks, Spies, and Timers Oh My!
  • 2. Lisa Backer Senior Software Engineer DockYard
  • 6.
  • 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.
  • 8. Unit Tests export const isMagicShoes = (shoes) => { }
  • 9. Unit Tests export const isMagicShoes = (shoes) => { } return shoes !== undefined && shoes.type === 'ruby';
  • 10. Unit Tests module('Unit | Utils | shoes', function() { module('isMagicShoes', function() { test('returns true only for magic shoes', function(assert) { }); }); }); export const isMagicShoes = (shoes) => { } return shoes !== undefined && shoes.type === 'ruby';
  • 11. Unit Tests module('Unit | Utils | shoes', function() { module('isMagicShoes', function() { test('returns true only for magic shoes', function(assert) { }); }); }); assert.deepEqual(isMagicShoes({ type: 'ruby' }), true); export const isMagicShoes = (shoes) => { } return shoes !== undefined && shoes.type === 'ruby';
  • 12. Unit Tests module('Unit | Utils | shoes', function() { module('isMagicShoes', function() { test('returns true only for magic shoes', function(assert) { }); }); }); assert.deepEqual(isMagicShoes({ type: 'ruby' }), true); assert.deepEqual(isMagicShoes({ type: 'silver' }), false); export const isMagicShoes = (shoes) => { } return shoes !== undefined && shoes.type === 'ruby';
  • 13. Unit Tests module('Unit | Utils | shoes', function() { module('isMagicShoes', function() { test('returns true only for magic shoes', function(assert) { }); }); }); assert.deepEqual(isMagicShoes({ type: 'ruby' }), true); assert.deepEqual(isMagicShoes({ type: 'silver' }), false); assert.deepEqual(isMagicShoes(), false); export const isMagicShoes = (shoes) => { } return shoes !== undefined && shoes.type === 'ruby';
  • 14. What Are We Testing?
  • 15. Container Tests Test the functionality around an Ember class instance by setting up the application's container.
  • 16. Container Tests export default class Magic extends Service { makeWish(shoes, wishCallback) { } }
  • 17. Container Tests export default class Magic extends Service { makeWish(shoes, wishCallback) { } } if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); }
  • 18. Container Tests export default class Magic extends Service { makeWish(shoes, wishCallback) { } } if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } module('Unit | Service | magic', function(hooks) { setupTest(hooks); test('makeWish', function(assert) { }); });
  • 19. Container Tests export default class Magic extends Service { makeWish(shoes, wishCallback) { } } if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } module('Unit | Service | magic', function(hooks) { setupTest(hooks); test('makeWish', function(assert) { }); }); let service = this.owner.lookup('service:magic'); let wish = () => { assert.ok(true, 'Wish was made'); }
  • 20. Container Tests export default class Magic extends Service { makeWish(shoes, wishCallback) { } } if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } module('Unit | Service | magic', function(hooks) { setupTest(hooks); test('makeWish', function(assert) { }); }); let service = this.owner.lookup('service:magic'); let wish = () => { assert.ok(true, 'Wish was made'); } service.makeWish({ type: 'ruby', clicked: 3 }, wish);
  • 21. Container Tests export default class Magic extends Service { makeWish(shoes, wishCallback) { } } if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } module('Unit | Service | magic', function(hooks) { setupTest(hooks); test('makeWish', function(assert) { }); }); let service = this.owner.lookup('service:magic'); let wish = () => { assert.ok(true, 'Wish was made'); } assert.expect(1); service.makeWish({ type: 'ruby', clicked: 3 }, wish);
  • 22. What Are We Testing?
  • 23. Rendering Tests Test the interactions between various parts of the application, such as behavior between UI controls.
  • 24. {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} Rendering Tests
  • 25. {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} Rendering Tests <button class="shoes" {{on "click" this.theresNoPlaceLikeHome}}> Shoes </button>
  • 26. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, onMakeWish() => {} actions: { theresNoPlaceLikeHome() { } } });
  • 27. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, onMakeWish() => {} actions: { theresNoPlaceLikeHome() { } } }); set(this, 'shoes.clicked', this.shoes.clicked + 1);
  • 28. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, onMakeWish() => {} actions: { theresNoPlaceLikeHome() { } } }); set(this, 'shoes.clicked', this.shoes.clicked + 1); this.magic.makeWish(this.shoes, () => { });
  • 29. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, onMakeWish() => {} actions: { theresNoPlaceLikeHome() { } } }); set(this, 'shoes.clicked', this.shoes.clicked + 1); this.magic.makeWish(this.shoes, () => { }); set(this, 'isHappy', true); this.onMakeWish();
  • 30. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, actions: { theresNoPlaceLikeHome() { } } }); set(this, 'shoes.clicked', this.shoes.clicked + 1); this.magic.makeWish(this.shoes, () => { set(this, 'isHappy', true); }); test('Dorothy is happy when magic happens', async function(assert) { this.set('shoes', { type: 'ruby', clicked: 0 }); });
  • 31. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, actions: { theresNoPlaceLikeHome() { } } }); set(this, 'shoes.clicked', this.shoes.clicked + 1); this.magic.makeWish(this.shoes, () => { set(this, 'isHappy', true); }); test('Dorothy is happy when magic happens', async function(assert) { this.set('shoes', { type: 'ruby', clicked: 0 }); }); await render(hbs`<Dorothy @shoes={{shoes}} />`); assert.notOk(this.element.querySelector('.happy')); assert.ok(this.element.querySelector('.scared'));
  • 32. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, actions: { theresNoPlaceLikeHome() { } } }); set(this, 'shoes.clicked', this.shoes.clicked + 1); this.magic.makeWish(this.shoes, () => { set(this, 'isHappy', true); }); test('Dorothy is happy when magic happens', async function(assert) { this.set('shoes', { type: 'ruby', clicked: 0 }); }); await render(hbs`<Dorothy @shoes={{shoes}} />`); assert.notOk(this.element.querySelector('.happy')); assert.ok(this.element.querySelector('.scared')); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes);
  • 33. Rendering Tests {{#if isHappy}} <p class="happy">Dorothy is Happy</p> {{else}} <p class="scared">Dorothy is scared and wants to go home</p> {{/if}} <button class="shoes" {{action "theresNoPlaceLikeHome"}}> Clicked {{this.shoes.clicked}} </button> export default Component.extend({ magic: inject(), isHappy: false, actions: { theresNoPlaceLikeHome() { } } }); set(this, 'shoes.clicked', this.shoes.clicked + 1); this.magic.makeWish(this.shoes, () => { set(this, 'isHappy', true); }); test('Dorothy is happy when magic happens', async function(assert) { this.set('shoes', { type: 'ruby', clicked: 0 }); }); await render(hbs`<Dorothy @shoes={{shoes}} />`); assert.notOk(this.element.querySelector('.happy')); assert.ok(this.element.querySelector('.scared')); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); assert.ok(this.element.querySelector('.happy')); assert.notOk(this.element.querySelector('.scared'));
  • 34. What Are We Testing?
  • 35. Application Tests Test user interaction and application flow in order to verify user stories or a feature from an end-user perspective.
  • 38. Application Tests test('Make a wish', async function(assert) { });
  • 39. Application Tests test('Make a wish', async function(assert) { }); this.owner.lookup('route:oz').model = function() { return { shoes: { type: 'ruby', clicked: 0 } }; };
  • 40. Application Tests test('Make a wish', async function(assert) { }); this.owner.lookup('route:oz').model = function() { return { shoes: { type: 'ruby', clicked: 0 } }; }; await visit('/oz'); assert.equal(currentURL(), '/oz');
  • 41. Application Tests test('Make a wish', async function(assert) { }); this.owner.lookup('route:oz').model = function() { return { shoes: { type: 'ruby', clicked: 0 } }; }; await visit('/oz'); assert.equal(currentURL(), '/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes');
  • 42. Application Tests test('Make a wish', async function(assert) { }); this.owner.lookup('route:oz').model = function() { return { shoes: { type: 'ruby', clicked: 0 } }; }; await visit('/oz'); assert.equal(currentURL(), '/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes'); assert.equal(currentURL(), '/kansas');
  • 43. What Are We Testing?
  • 44.
  • 45.
  • 46. Unit Tests export const isMagicShoes = (shoes) => { return shoes !== undefined && shoes.type === 'ruby'; }
  • 47. Unit Tests export const isMagicShoes = (shoes) => { return shoes !== undefined && shoes.type === 'silver'; }
  • 48. Unit Tests module('Unit | Utils | shoes', function() { module('isMagicShoes', function() { test('returns true only for magic shoes', function(assert) { assert.deepEqual(isMagicShoes({ type: 'ruby' }), true); assert.deepEqual(isMagicShoes({ type: 'silver' }), false); assert.deepEqual(isMagicShoes(), false); }); }); });
  • 49. Unit Tests module('Unit | Utils | shoes', function() { module('isMagicShoes', function() { test('returns true only for magic shoes', function(assert) { assert.deepEqual(isMagicShoes({ type: 'ruby' }), false); assert.deepEqual(isMagicShoes({ type: 'silver' }), true); assert.deepEqual(isMagicShoes(), false); }); }); });
  • 50. Container Tests module('Unit | Service | magic', function(hooks) { setupTest(hooks); test('makeWish', function(assert) { assert.expect(1); let service = this.owner.lookup('service:magic'); let wish = () => { assert.ok(true, 'Wish was made'); } service.makeWish({ type: 'ruby', clicked: 3 }, wish); }); });
  • 51. Container Tests module('Unit | Service | magic', function(hooks) { setupTest(hooks); test('makeWish', function(assert) { assert.expect(1); let service = this.owner.lookup('service:magic'); let wish = () => { assert.ok(true, 'Wish was made'); } service.makeWish({ type: 'silver', clicked: 3 }, wish); }); });
  • 52. Rendering Tests test('Dorothy is happy when magic happens', async function(assert) { this.set('shoes', { type: 'ruby', clicked: 0 }); await render(hbs`<Dorothy @shoes={{shoes}} />`); assert.notOk(this.element.querySelector('.happy')); assert.ok(this.element.querySelector('.scared')); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); assert.ok(this.element.querySelector('.happy')); assert.notOk(this.element.querySelector('.scared')); });
  • 53. Rendering Tests test('Dorothy is happy when magic happens', async function(assert) { this.set('shoes', { type: 'silver', clicked: 0 }); await render(hbs`<Dorothy @shoes={{shoes}} />`); assert.notOk(this.element.querySelector('.happy')); assert.ok(this.element.querySelector('.scared')); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); assert.ok(this.element.querySelector('.happy')); assert.notOk(this.element.querySelector('.scared')); });
  • 54. Application Tests test('Make a wish', async function(assert) { this.owner.lookup('route:oz').model = function() { return { shoes: { type: 'ruby', clicked: 0 } }; }; await visit('/oz'); assert.equal(currentURL(), '/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes'); assert.equal(currentURL(), '/kansas'); });
  • 55. Application Tests test('Make a wish', async function(assert) { this.owner.lookup('route:oz').model = function() { return { shoes: { type: 'silver', clicked: 0 } }; }; await visit('/oz'); assert.equal(currentURL(), '/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes'); assert.equal(currentURL(), '/kansas'); });
  • 56.
  • 57. What Are We Testing?
  • 58. System Under Test "Short for 'whatever we are testing' and is always defined from the perspective of the test." xUnit Patterns
  • 59.
  • 63. if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } test('makeWish', function(assert) { }); let service = this.owner.lookup('service:magic'); let wish = () => { assert.ok(true, 'Wish was made'); } assert.expect(1); service.makeWish(myShoes, wish); SinonJS: Spies
  • 64. if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } SinonJS: Spies test('makeWish', function(assert) { });     let wish = sinon.spy();     let service = this.owner.lookup('service:magic');
  • 65. if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } SinonJS: Spies test('makeWish', function(assert) { });     service.makeWish(myShoes, wish);     let wish = sinon.spy();     let service = this.owner.lookup('service:magic');
  • 66. if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } SinonJS: Spies test('makeWish', function(assert) { });     service.makeWish(myShoes, wish);     let wish = sinon.spy();     let service = this.owner.lookup('service:magic');     assert.ok(wish.calledOnce);
  • 67. import * as ShoeUtils from 'oz/utils/shoes'; test('shoes are validated with each heel click', async function(assert) { }); SinonJS: Spies
  • 68. import * as ShoeUtils from 'oz/utils/shoes'; test('shoes are validated with each heel click', async function(assert) { }); SinonJS: Spies const spy = sinon.spy(ShoeUtils, 'isMagicShoes'); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 69. import * as ShoeUtils from 'oz/utils/shoes'; test('shoes are validated with each heel click', async function(assert) { }); SinonJS: Spies const spy = sinon.spy(ShoeUtils, 'isMagicShoes'); await render(hbs`<Dorothy @shoes={{shoes}} />`); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes);
  • 70. import * as ShoeUtils from 'oz/utils/shoes'; test('shoes are validated with each heel click', async function(assert) { }); SinonJS: Spies const spy = sinon.spy(ShoeUtils, 'isMagicShoes'); await render(hbs`<Dorothy @shoes={{shoes}} />`); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); assert.ok(this.element.querySelector('.happy')); assert.equal(spy.callCount, 3);
  • 72. SinonJS: Stubs export default class Magic extends Service { makeWish(shoes, wishCallback) { if (isMagicShoes(shoes) && clicked === 3) { wishCallback(); } } } let clicked = shoes.clicked;
  • 73. SinonJS: Stubs 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');
  • 74. SinonJS: Stubs test('Dorothy is happy when magic happens', async function(assert) { });
  • 75. SinonJS: Stubs test('Dorothy is happy when magic happens', async function(assert) { }); const magicService = this.owner.lookup('service:magic'); const stub = sinon.stub(magicService.ajax, 'request') .onCall(0).resolves(1) .onCall(1).resolves(3); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 76. SinonJS: Stubs test('Dorothy is happy when magic happens', async function(assert) { }); const shoes = this.element.querySelector('.shoes'); await click(shoes); assert.notOk(this.element.querySelector('.happy')); const magicService = this.owner.lookup('service:magic'); const stub = sinon.stub(magicService.ajax, 'request') .onCall(0).resolves(1) .onCall(1).resolves(3); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 77. SinonJS: Stubs test('Dorothy is happy when magic happens', async function(assert) { }); const shoes = this.element.querySelector('.shoes'); await click(shoes); assert.notOk(this.element.querySelector('.happy')); const magicService = this.owner.lookup('service:magic'); const stub = sinon.stub(magicService.ajax, 'request') .onCall(0).resolves(1) .onCall(1).resolves(3); await render(hbs`<Dorothy @shoes={{shoes}} />`); await click(shoes);
  • 78. SinonJS: Stubs test('Dorothy is happy when magic happens', async function(assert) { }); const shoes = this.element.querySelector('.shoes'); await click(shoes); assert.notOk(this.element.querySelector('.happy')); const magicService = this.owner.lookup('service:magic'); const stub = sinon.stub(magicService.ajax, 'request') .onCall(0).resolves(1) .onCall(1).resolves(3); await render(hbs`<Dorothy @shoes={{shoes}} />`); await click(shoes); assert.ok(this.element.querySelector('.happy')); assert.equal(stub.callCount.length, 2);
  • 80. SinonJS: Fakes if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } test('makeWish', function(assert) { });     service.makeWish({ type: 'ruby', clicked: 3 }, wish);     let wish = sinon.spy();     assert.ok(spy.calledOnce);     let service = this.owner.lookup('service:magic');
  • 81. SinonJS: Fakes if (isMagicShoes(shoes) && shoes.clicked === 3) { wishCallback(); } test('makeWish', function(assert) { });     service.makeWish({ type: 'ruby', clicked: 3 }, wish);     let wish = sinon.fake();     assert.ok(spy.calledOnce);     let service = this.owner.lookup('service:magic');
  • 82. test('no magic wishes for non-magic shoes', async function(assert) { }); const spy = sinon.spy(ShoeUtils, 'isMagicShoes'); SinonJS: Fakes const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); assert.ok(spy.calledOnce); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 83. test('no magic wishes for non-magic shoes', async function(assert) { }); SinonJS: Fakes const fake = sinon.fake.returns(false); sinon.replace(ShoeUtils, 'isMagicShoes', fake); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); assert.equal(fake.callCount, 3); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 85. SinonJS: Mocks test('clicking three times with magic shoes', async function(assert) { });
  • 86. SinonJS: Mocks test('clicking three times with magic shoes', async function(assert) { }); const mock = sinon.mock(ShoeUtils); mock.expects('isMagicShoes').exactly(3).returns(true); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 87. SinonJS: Mocks test('clicking three times with magic shoes', async function(assert) { }); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); const mock = sinon.mock(ShoeUtils); mock.expects('isMagicShoes').exactly(3).returns(true); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 88. SinonJS: Mocks test('clicking three times with magic shoes', async function(assert) { }); const shoes = this.element.querySelector('.shoes'); await click(shoes); await click(shoes); await click(shoes); assert.ok(this.element.querySelector('.happy')); mock.verify(); const mock = sinon.mock(ShoeUtils); mock.expects('isMagicShoes').exactly(3).returns(true); await render(hbs`<Dorothy @shoes={{shoes}} />`);
  • 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.
  • 96. SinonJS: Timers actions: { goHome() { this.transitionToRoute('kansas'); } }
  • 97. SinonJS: Timers actions: { goHome() { this.transitionToRoute('kansas'); } } // ooohhh suspense setTimeout(() => { }, 5000);
  • 98. SinonJS: Timers actions: { goHome() { // ooohhh suspense setTimeout(() => { this.transitionToRoute('kansas'); }, 5000); } } test('Make a wish', async function(assert) {   await visit('/oz');   assert.equal(currentURL(), '/oz');   await click('.shoes');   await click('.shoes');   await click('.shoes');   assert.equal(currentURL(), '/oz'); }); assert.equal(currentURL(), '/kansas'); // ¯_(ツ)_/¯
  • 99. SinonJS: Timers actions: { goHome() { // ooohhh suspense setTimeout(() => { this.transitionToRoute('kansas'); }, 5000); } } test('Make a wish', async function(assert) { const clock = sinon.useFakeTimers(); });
  • 100. SinonJS: Timers actions: { goHome() { // ooohhh suspense setTimeout(() => { this.transitionToRoute('kansas'); }, 5000); } } test('Make a wish', async function(assert) { const clock = sinon.useFakeTimers(); }); await visit('/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes');
  • 101. SinonJS: Timers actions: { goHome() { // ooohhh suspense setTimeout(() => { this.transitionToRoute('kansas'); }, 5000); } } test('Make a wish', async function(assert) { const clock = sinon.useFakeTimers(); }); await visit('/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes'); assert.equal(currentURL(), '/oz');
  • 102. SinonJS: Timers actions: { goHome() { // ooohhh suspense setTimeout(() => { this.transitionToRoute('kansas'); }, 5000); } } test('Make a wish', async function(assert) { const clock = sinon.useFakeTimers(); }); await visit('/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes'); assert.equal(currentURL(), '/oz'); clock.runAll(); await settled();
  • 103. SinonJS: Timers actions: { goHome() { // ooohhh suspense setTimeout(() => { this.transitionToRoute('kansas'); }, 5000); } } test('Make a wish', async function(assert) { const clock = sinon.useFakeTimers(); }); await visit('/oz'); await click('.shoes'); await click('.shoes'); await click('.shoes'); assert.equal(currentURL(), '/oz'); clock.runAll(); await settled(); assert.equal(currentURL(), '/kansas');
  • 112. TestDouble.js • Combines all into single test double function
  • 113. TestDouble.js • Combines all into single test double function • Allows for dynamic object stubbing
  • 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)
  • 118. Jest
  • 119. Jest
  • 120. Jest
  • 121. Jest • Full set of assertions using a chai-like syntax
  • 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
  • 127.
  • 130. Mocking makes refactoring harder Mocking is a Code Smell Mocking violates DRY
  • 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
  • 134.
  • 135. Don't mock what you don't own API Data Files HTTP
  • 136. Don't mock what you don't own API Data Files HTTPService
  • 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
  • 148. What Are We Testing?
  • 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