From 3628875dcc9563795a6c751fc311a772ed44b18f Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Mon, 23 May 2016 11:04:39 -0400 Subject: [PATCH 1/2] 100% test coverage % for linodes/actions/create.js --- test/linodes/actions/create.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/linodes/actions/create.spec.js b/test/linodes/actions/create.spec.js index d988787b69b..1322d4bfb02 100644 --- a/test/linodes/actions/create.spec.js +++ b/test/linodes/actions/create.spec.js @@ -192,6 +192,9 @@ describe("linodes/actions/create", () => { const _update = sinon.stub(linode_actions, "updateLinodeUntil", update); await func(dispatch, getState); expect(update.calledWith(response.linode.id)).to.be.true; + expect(update.args[0][1]).to.be.a("function"); + expect(update.args[0][1]({ state: "provisioning" })).to.be.false; + expect(update.args[0][1]({ state: "powered_off" })).to.be.true; }, response, state); })); }); From 09605192509d2a7a61a0b2acee5140ba53b9aa5e Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Tue, 24 May 2016 09:04:58 -0400 Subject: [PATCH 2/2] Get sinon sandboxes working correctly --- test/actions/api/linodes.spec.js | 41 ++- test/api-store.spec.js | 455 ++++++++++++++-------------- test/linodes/actions/create.spec.js | 58 ++-- test/mocks.js | 2 +- 4 files changed, 294 insertions(+), 262 deletions(-) diff --git a/test/actions/api/linodes.spec.js b/test/actions/api/linodes.spec.js index 2c0c610c958..2f1cafa29cb 100644 --- a/test/actions/api/linodes.spec.js +++ b/test/actions/api/linodes.spec.js @@ -1,4 +1,5 @@ import sinon from 'sinon'; +import { expect } from 'chai'; import { powerOnLinode, powerOffLinode, @@ -13,20 +14,30 @@ describe("actions/linodes/power", sinon.test(() => { linode: { id: "foo", state: "booting" } }; + let sandbox = null; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + it('returns linode power boot status', async () => { - await mock_context(async ({ + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const f = powerOnLinode("foo"); await f(dispatch, getState); - sinon.assert.calledWith(fetchStub, - auth.token, '/linodes/foo/boot', { method: "POST" }); - sinon.assert.calledWith(dispatch, { + expect(fetchStub.calledWith( + auth.token, '/linodes/foo/boot', { method: "POST" })).to.be.true; + expect(dispatch.calledWith({ type: UPDATE_LINODE, linode: { id: "foo", state: "booting" } - }); + })).to.be.true; }, mock_booting_response); }); @@ -36,19 +47,19 @@ describe("actions/linodes/power", sinon.test(() => { }; it('returns linode power shutdown status', async () => { - await mock_context(async ({ + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const f = powerOffLinode("foo"); await f(dispatch, getState); - sinon.assert.calledWith(fetchStub, - auth.token, '/linodes/foo/shutdown', { method: "POST" }); - sinon.assert.calledWith(dispatch, { + expect(fetchStub.calledWith( + auth.token, '/linodes/foo/shutdown', { method: "POST" })).to.be.true; + expect(dispatch.calledWith({ type: UPDATE_LINODE, linode: { id: "foo", state: "shutting_down" } - }); + })).to.be.true; }, mock_shutting_down_response); }); @@ -58,19 +69,19 @@ describe("actions/linodes/power", sinon.test(() => { }; it('returns linode power reboot status', async () => { - await mock_context(async ({ + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const f = rebootLinode("foo"); await f(dispatch, getState); - sinon.assert.calledWith(fetchStub, - auth.token, '/linodes/foo/reboot', { method: "POST" }); - sinon.assert.calledWith(dispatch, { + expect(fetchStub.calledWith( + auth.token, '/linodes/foo/reboot', { method: "POST" })).to.be.true; + expect(dispatch.calledWith({ type: UPDATE_LINODE, linode: { id: "foo", state: "rebooting" } - }); + })).to.be.true; }, mock_rebooting_response); }); })); diff --git a/test/api-store.spec.js b/test/api-store.spec.js index 192fe9bc917..8ca82e23a1b 100644 --- a/test/api-store.spec.js +++ b/test/api-store.spec.js @@ -20,268 +20,281 @@ const mock_foobars_response = { page: 1 }; -describe("api-store/make_api_list", () => { - it('should handle initial state', () => { - const s = make_api_list("foobars", "foobar", { - update_singular: "UPDATE_ONE", - update_many: "UPDATE_MANY", - delete_one: "DELETE_ONE", - }); +describe("api-store", () => { + let sandbox = null; - expect( - s(undefined, {}) - ).to.be.eql({ - pagesFetched: [ ], totalPages: -1, foobars: {}, - _singular: "foobar", _plural: "foobars" - }); + beforeEach(() => { + sandbox = sinon.sandbox.create(); }); - it('should not handle actions not specified', () => { - const s = make_api_list("foobars", "foobar", { - update_singular: "UPDATE_ONE" - }); - const state = s(undefined, {}); - deepFreeze(state); - - expect( - s(state, { type: "DELETE_ONE" }) - ).to.be.eql(state); + afterEach(() => { + sandbox.restore(); }); - it('should handle updating many records', () => { - const s = make_api_list("foobars", "foobar", { - update_many: "UPDATE_MANY" - }); - - const state = s(undefined, {}); - deepFreeze(state); + describe("api-store/make_api_list", () => { + it('should handle initial state', () => { + const s = make_api_list("foobars", "foobar", { + update_singular: "UPDATE_ONE", + update_many: "UPDATE_MANY", + delete_one: "DELETE_ONE", + }); - const result = s(state, { - type: "UPDATE_MANY", - response: mock_foobars_response + expect( + s(undefined, {}) + ).to.be.eql({ + pagesFetched: [ ], totalPages: -1, foobars: {}, + _singular: "foobar", _plural: "foobars" + }); }); - const foobars = expect(result) - .to.have.property('foobars') - .which.has.keys('foobar_1', 'foobar_2'); - }); + it('should not handle actions not specified', () => { + const s = make_api_list("foobars", "foobar", { + update_singular: "UPDATE_ONE" + }); + const state = s(undefined, {}); + deepFreeze(state); - it('should add internal properties to objects', () => { - const s = make_api_list("foobars", "foobar", { - update_many: "UPDATE_MANY" + expect( + s(state, { type: "DELETE_ONE" }) + ).to.be.eql(state); }); - const state = s(undefined, {}); - deepFreeze(state); - - const result = s(state, { - type: "UPDATE_MANY", - response: { - foobars: [ - { id: "foobar_1" }, - { id: "foobar_2" } - ], - total_pages: 3, - total_results: 25 * 3 - 4, - page: 1 - } - }); + it('should handle updating many records', () => { + const s = make_api_list("foobars", "foobar", { + update_many: "UPDATE_MANY" + }); - const foobars = expect(result) - .to.have.property('foobars') - .which.has.property('foobar_1') - .which.has.property('_polling'); - }); + const state = s(undefined, {}); + deepFreeze(state); - it('should invoke custom transforms', () => { - const s = make_api_list("foobars", "foobar", { - update_many: "UPDATE_MANY" - }, o => ({ ...o, test: 1234 })); - - const state = s(undefined, {}); - deepFreeze(state); - - const result = s(state, { - type: "UPDATE_MANY", - response: { - foobars: [ - { id: "foobar_1" }, - { id: "foobar_2" } - ], - total_pages: 3, - total_results: 25 * 3 - 4, - page: 1 - } - }); - - const foobars = expect(result) - .to.have.property('foobars') - .which.has.property('foobar_1') - .which.has.property('test') - .which.equals(1234); - }); + const result = s(state, { + type: "UPDATE_MANY", + response: mock_foobars_response + }); - it('should handle adding a single resource', () => { - const s = make_api_list("foobars", "foobar", { - update_singular: "FUCK_WILL_SMITH (the actor)" + const foobars = expect(result) + .to.have.property('foobars') + .which.has.keys('foobar_1', 'foobar_2'); }); - const state = s(undefined, {}); - deepFreeze(state); + it('should add internal properties to objects', () => { + const s = make_api_list("foobars", "foobar", { + update_many: "UPDATE_MANY" + }); - const result = s(state, { - type: "FUCK_WILL_SMITH (the actor)", - foobar: { id: "foobar_1" } - }); - - expect(result) - .to.have.property('foobars') - .which.has.keys('foobar_1'); - }); + const state = s(undefined, {}); + deepFreeze(state); + + const result = s(state, { + type: "UPDATE_MANY", + response: { + foobars: [ + { id: "foobar_1" }, + { id: "foobar_2" } + ], + total_pages: 3, + total_results: 25 * 3 - 4, + page: 1 + } + }); - it('should handle updating a single resource', () => { - const s = make_api_list("foobars", "foobar", { - update_singular: "FUCK_DODSON (the jurrasic park guy)" + const foobars = expect(result) + .to.have.property('foobars') + .which.has.property('foobar_1') + .which.has.property('_polling'); }); - let state = s(undefined, {}); - state = { - ...state, - foobars: { - ...state.foobars, - foobar_1: { id: "foobar_1" } - } - }; - deepFreeze(state); - - const result = s(state, { - type: "FUCK_DODSON (the jurrasic park guy)", - foobar: { id: "foobar_1", name: "hello" } + it('should invoke custom transforms', () => { + const s = make_api_list("foobars", "foobar", { + update_many: "UPDATE_MANY" + }, o => ({ ...o, test: 1234 })); + + const state = s(undefined, {}); + deepFreeze(state); + + const result = s(state, { + type: "UPDATE_MANY", + response: { + foobars: [ + { id: "foobar_1" }, + { id: "foobar_2" } + ], + total_pages: 3, + total_results: 25 * 3 - 4, + page: 1 + } + }); + + const foobars = expect(result) + .to.have.property('foobars') + .which.has.property('foobar_1') + .which.has.property('test') + .which.equals(1234); }); - - expect(result) - .to.have.property('foobars') - .which.has.property('foobar_1') - .which.has.property('name') - .which.equals('hello'); - }); - it('should handle deleting a single resource', () => { - const s = make_api_list("foobars", "foobar", { - delete_one: "FUCK_MARQUES (the R&B artist)" - }); + it('should handle adding a single resource', () => { + const s = make_api_list("foobars", "foobar", { + update_singular: "FUCK_WILL_SMITH (the actor)" + }); - let state = s(undefined, {}); - state = { - ...state, - foobars: { - ...state.foobars, - foobar_1: { id: "foobar_1" } - } - }; - deepFreeze(state); - - const result = s(state, { - type: "FUCK_MARQUES (the R&B artist)", - id: "foobar_1" - }); - - expect(result) - .to.have.property('foobars') - .which/*.does*/.not.have.property('foobar_1'); - }); + const state = s(undefined, {}); + deepFreeze(state); -}); + const result = s(state, { + type: "FUCK_WILL_SMITH (the actor)", + foobar: { id: "foobar_1" } + }); + + expect(result) + .to.have.property('foobars') + .which.has.keys('foobar_1'); + }); -describe("api-store/make_fetch_page", sinon.test(() => { - it('returns a function that itself returns a function', () => { - const f = make_fetch_page("FETCH_FOOBARS", "foobars"); - expect(f).to.be.a("function"); - expect(f()).to.be.a("function"); - }); + it('should handle updating a single resource', () => { + const s = make_api_list("foobars", "foobar", { + update_singular: "FUCK_DODSON (the jurrasic park guy)" + }); - it('fetches a page of items from the API', async () => { - await mock_context(async ({ - auth, dispatch, getState, fetchStub - }) => { - const f = make_fetch_page("FETCH_FOOBARS", "foobars"); - const p = f(); + let state = s(undefined, {}); + state = { + ...state, + foobars: { + ...state.foobars, + foobar_1: { id: "foobar_1" } + } + }; + deepFreeze(state); + + const result = s(state, { + type: "FUCK_DODSON (the jurrasic park guy)", + foobar: { id: "foobar_1", name: "hello" } + }); + + expect(result) + .to.have.property('foobars') + .which.has.property('foobar_1') + .which.has.property('name') + .which.equals('hello'); + }); - await p(dispatch, getState); + it('should handle deleting a single resource', () => { + const s = make_api_list("foobars", "foobar", { + delete_one: "FUCK_MARQUES (the R&B artist)" + }); - sinon.assert.calledWith(fetchStub, - auth.token, '/foobars?page=1'); - sinon.assert.calledWith(dispatch, { - type: "FETCH_FOOBARS", - response: mock_foobars_response + let state = s(undefined, {}); + state = { + ...state, + foobars: { + ...state.foobars, + foobar_1: { id: "foobar_1" } + } + }; + deepFreeze(state); + + const result = s(state, { + type: "FUCK_MARQUES (the R&B artist)", + id: "foobar_1" }); - }, mock_foobars_response); + + expect(result) + .to.have.property('foobars') + .which/*.does*/.not.have.property('foobar_1'); + }); + }); - it('fetches the requested page', async () => { - await mock_context(async ({ - auth, dispatch, getState, fetchStub - }) => { + describe("api-store/make_fetch_page", () => { + it('returns a function that itself returns a function', () => { const f = make_fetch_page("FETCH_FOOBARS", "foobars"); - const p = f(1); + expect(f).to.be.a("function"); + expect(f()).to.be.a("function"); + }); - await p(dispatch, getState); + it('fetches a page of items from the API', async () => { + await mock_context(sandbox, async ({ + auth, dispatch, getState, fetchStub + }) => { + const f = make_fetch_page("FETCH_FOOBARS", "foobars"); + const p = f(); + + await p(dispatch, getState); + + expect(fetchStub.calledWith( + auth.token, '/foobars?page=1')).to.be.true; + expect(dispatch.calledWith({ + type: "FETCH_FOOBARS", + response: mock_foobars_response + })).to.be.true; + }, mock_foobars_response); + }); - sinon.assert.calledWith(fetchStub, - auth.token, '/foobars?page=2'); - }, mock_foobars_response); - }); -})); + it('fetches the requested page', async () => { + await mock_context(sandbox, async ({ + auth, dispatch, getState, fetchStub + }) => { + const f = make_fetch_page("FETCH_FOOBARS", "foobars"); + const p = f(1); + + await p(dispatch, getState); -describe("api-store/make_update_item", sinon.test(() => { - it('returns a function that itself returns a function', () => { - const f = make_update_item("UPDATE_FOOBAR", "foobars", "foobar"); - expect(f).to.be.a("function"); - expect(f()).to.be.a("function"); + expect(fetchStub.calledWith( + auth.token, '/foobars?page=2')).to.be.true; + }, mock_foobars_response); + }); }); - it('fetches an item from the API', async () => { - await mock_context(async ({ - auth, dispatch, getState, fetchStub - }) => { + describe("api-store/make_update_item", () => { + it('returns a function that itself returns a function', () => { const f = make_update_item("UPDATE_FOOBAR", "foobars", "foobar"); - const p = f("foobar_1"); - - await p(dispatch, getState); - - sinon.assert.calledWith(fetchStub, - auth.token, '/foobars/foobar_1'); - sinon.assert.calledWith(dispatch, { - type: "UPDATE_FOOBAR", - foobar: mock_foobars_response.foobars[0] - }); - }, mock_foobars_response.foobars[0]); - }); -})); + expect(f).to.be.a("function"); + expect(f()).to.be.a("function"); + }); -describe("api-store/make_delete_item", sinon.test(() => { - it('returns a function that itself returns a function', () => { - const f = make_delete_item("DELETE_FOOBAR", "foobars"); - expect(f).to.be.a("function"); - expect(f()).to.be.a("function"); + it('fetches an item from the API', async () => { + await mock_context(sandbox, async ({ + auth, dispatch, getState, fetchStub + }) => { + const f = make_update_item("UPDATE_FOOBAR", "foobars", "foobar"); + const p = f("foobar_1"); + + await p(dispatch, getState); + + expect(fetchStub.calledWith( + auth.token, '/foobars/foobar_1')).to.be.true; + expect(dispatch.calledWith({ + type: "UPDATE_FOOBAR", + foobar: mock_foobars_response.foobars[0] + })).to.be.true; + }, mock_foobars_response.foobars[0]); + }); }); - const no_response = {}; - it('performs the API request', async () => { - await mock_context(async ({ - auth, dispatch, getState, fetchStub - }) => { + describe("api-store/make_delete_item", () => { + it('returns a function that itself returns a function', () => { const f = make_delete_item("DELETE_FOOBAR", "foobars"); - const p = f("foobar_1"); - - await p(dispatch, getState); + expect(f).to.be.a("function"); + expect(f()).to.be.a("function"); + }); - sinon.assert.calledWith(fetchStub, - auth.token, '/foobars/foobar_1', { method: "DELETE" }); - sinon.assert.calledWith(dispatch, { - type: "DELETE_FOOBAR", - id: "foobar_1" - }); - }, no_response); + const no_response = {}; + it('performs the API request', async () => { + await mock_context(sandbox, async ({ + auth, dispatch, getState, fetchStub + }) => { + const f = make_delete_item("DELETE_FOOBAR", "foobars"); + const p = f("foobar_1"); + + await p(dispatch, getState); + + expect(fetchStub.calledWith( + auth.token, '/foobars/foobar_1', { method: "DELETE" })).to.be.true; + expect(dispatch.calledWith({ + type: "DELETE_FOOBAR", + id: "foobar_1" + })).to.be.true; + }, no_response); + }); }); -})); +}); + diff --git a/test/linodes/actions/create.spec.js b/test/linodes/actions/create.spec.js index 1322d4bfb02..98b5db12b2c 100644 --- a/test/linodes/actions/create.spec.js +++ b/test/linodes/actions/create.spec.js @@ -78,6 +78,16 @@ describe("linodes/actions/create", () => { }); describe("createLinode", () => { + let sandbox = null; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + const state = { authentication: { token: 'token' @@ -103,18 +113,18 @@ describe("linodes/actions/create", () => { expect(actions.createLinode()).to.be.a("function"); }); - it("should call getState() once", sinon.test(async () => { - await mock_context(async ({ + it("should call getState() once", async () => { + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const func = actions.createLinode(); await func(dispatch, getState); expect(getState.calledOnce).to.be.true; }, response, state); - })); + }); - it("should dispatch a TOGGLE_CREATING action", sinon.test(async () => { - await mock_context(async ({ + it("should dispatch a TOGGLE_CREATING action", async () => { + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const func = actions.createLinode(); @@ -123,10 +133,10 @@ describe("linodes/actions/create", () => { type: actions.TOGGLE_CREATING })).to.be.true; }, response, state); - })); + }); - it("should perform an HTTP POST to /linodes", sinon.test(async () => { - await mock_context(async ({ + it("should perform an HTTP POST to /linodes", async () => { + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const func = actions.createLinode(); @@ -144,11 +154,10 @@ describe("linodes/actions/create", () => { } )).to.be.true; }, response, state); - })); + }); - it("should dispatch an UPDATE_LINODE action with the new linode", - sinon.test(async () => { - await mock_context(async ({ + it("should dispatch an UPDATE_LINODE action with the new linode", async () => { + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const func = actions.createLinode(); @@ -158,11 +167,10 @@ describe("linodes/actions/create", () => { linode: response.linode })).to.be.true; }, response, state); - })); + }); - it("should dispatch a routing action to navigate to the detail page", - sinon.test(async () => { - await mock_context(async ({ + it("should dispatch a routing action to navigate to the detail page", async () => { + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const func = actions.createLinode(); @@ -171,31 +179,31 @@ describe("linodes/actions/create", () => { pushPath(`/linodes/${response.linode.id}`) )).to.be.true; }, response, state); - })); + }); - it("should dispatch a CLEAR_FORM action", sinon.test(async () => { - await mock_context(async ({ + it("should dispatch a CLEAR_FORM action", async () => { + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const func = actions.createLinode(); await func(dispatch, getState); expect(dispatch.calledWith({ type: actions.CLEAR_FORM })).to.be.true; }, response, state); - })); + }); - it("should update the linode until it finishes provisioning", sinon.test(async () => { - await mock_context(async ({ + it("should update the linode until it finishes provisioning", async () => { + await mock_context(sandbox, async ({ auth, dispatch, getState, fetchStub }) => { const func = actions.createLinode(); - const update = sinon.spy(() => { }); - const _update = sinon.stub(linode_actions, "updateLinodeUntil", update); + const update = sandbox.spy(() => { }); + const _update = sandbox.stub(linode_actions, "updateLinodeUntil", update); await func(dispatch, getState); expect(update.calledWith(response.linode.id)).to.be.true; expect(update.args[0][1]).to.be.a("function"); expect(update.args[0][1]({ state: "provisioning" })).to.be.false; expect(update.args[0][1]({ state: "powered_off" })).to.be.true; }, response, state); - })); + }); }); }); diff --git a/test/mocks.js b/test/mocks.js index 5b91e46de4e..0108dc1471a 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -1,7 +1,7 @@ import sinon from 'sinon'; import * as fetch from '~/fetch'; -export const mock_context = async (f, rsp, state={}) => { +export const mock_context = async (sandbox, f, rsp, state={}) => { const auth = { token: 'token' }; let getState = sinon.stub().returns({ authentication: auth,