i'm running a test with mocha, chai and sinon, the main purpose is to mock localStorage and inject it as a global variable, so i can call it later as localStorage
. The main issue is that i'm using webpack with babel using the node strategy for testing (for faster testing and dev), so i'm using rewire to overwrite the globals.
This is my code:
import assert from 'assert';
import rewire from 'rewire';
import sinon from 'sinon';
import {expect} from 'chai';
import todos from './mocks/todos';
import LocalStorage from './mocks/localStorage.js';
let Todo = rewire('../src/store/todo');
let todo,
localStorage,
localStorageMock,
task = { id: 5, title: 'new task', pleted: false };
describe('Todo Model', () => {
beforeEach(function() {
localStorage = new LocalStorage();
localStorageMock = sinon.mock(localStorage);
Todo.__set__('globals.localStorage', localStorage);
todo = new Todo();
});
describe('add todo', () => {
it('save the todo to localStorage',() =>{
localStorageMock.expects('setItem').once();
todo.create(task);
localStorageMock.verify();
expect(localStorage.data['todos']).to.be.a('string');
});
});
});
EDIT
here is the todo module
import _ from 'ramda';
import {Maybe} from 'ramda-fantasy';
let log = (x, y) => console.log(x, y);
// getItem :: storage, item -> string
let getItem = _.curry((storage, x) => storage.getItem(x));
// parse :: string -> json
let parse = (x) => x ? JSON.parse(x): null;
// stringify :: json -> string
let stringify = (x) => JSON.stringify(x);
// setItem :: storage, string, jsonStringified
let setItem = _.curry((storage, x, y) => storage.setItem(x, y));
export default class todo {
constructor(){
this.name = 'name';
this.storage = localStorage;
this.storage.getItem('todos') ? '' : this.storage.setItem('todos', '');
}
create(todo) {
this.new = _pose(_.map(_pose(setItem(this.storage, 'todos'), stringify, _.append(todo), parse)), Maybe, getItem(this.storage));
this.new('todos');
}
}
The test fails because setItem
is never called, but in the code is called. If u return the instance of localStorage and pare it with my mock localStorageMock === returnedLocalStorage
gives me false.
This is because rewire copy each method separately creating a new object.
When i change the beforeEach block:
Todo.__set__('globals.localStorage', localStorage);
to
Todo.__set__('localStorage', localStorage);
it throws a referenceError:
1) Todo Model "before each" hook for "have a create method":
ReferenceError: localStorage is not defined
at Function.eval (eval at __set__ (src/store/todo.js:18:26), <anonymous>:1:14)
at Function.__set__ (src/store/todo.js:18:26)
at Context.<anonymous> (spec/index.spec.js:18:5)
How do you test if a service with a global dependency was called?
i'm running a test with mocha, chai and sinon, the main purpose is to mock localStorage and inject it as a global variable, so i can call it later as localStorage
. The main issue is that i'm using webpack with babel using the node strategy for testing (for faster testing and dev), so i'm using rewire to overwrite the globals.
This is my code:
import assert from 'assert';
import rewire from 'rewire';
import sinon from 'sinon';
import {expect} from 'chai';
import todos from './mocks/todos';
import LocalStorage from './mocks/localStorage.js';
let Todo = rewire('../src/store/todo');
let todo,
localStorage,
localStorageMock,
task = { id: 5, title: 'new task', pleted: false };
describe('Todo Model', () => {
beforeEach(function() {
localStorage = new LocalStorage();
localStorageMock = sinon.mock(localStorage);
Todo.__set__('globals.localStorage', localStorage);
todo = new Todo();
});
describe('add todo', () => {
it('save the todo to localStorage',() =>{
localStorageMock.expects('setItem').once();
todo.create(task);
localStorageMock.verify();
expect(localStorage.data['todos']).to.be.a('string');
});
});
});
EDIT
here is the todo module
import _ from 'ramda';
import {Maybe} from 'ramda-fantasy';
let log = (x, y) => console.log(x, y);
// getItem :: storage, item -> string
let getItem = _.curry((storage, x) => storage.getItem(x));
// parse :: string -> json
let parse = (x) => x ? JSON.parse(x): null;
// stringify :: json -> string
let stringify = (x) => JSON.stringify(x);
// setItem :: storage, string, jsonStringified
let setItem = _.curry((storage, x, y) => storage.setItem(x, y));
export default class todo {
constructor(){
this.name = 'name';
this.storage = localStorage;
this.storage.getItem('todos') ? '' : this.storage.setItem('todos', '');
}
create(todo) {
this.new = _.pose(_.map(_.pose(setItem(this.storage, 'todos'), stringify, _.append(todo), parse)), Maybe, getItem(this.storage));
this.new('todos');
}
}
The test fails because setItem
is never called, but in the code is called. If u return the instance of localStorage and pare it with my mock localStorageMock === returnedLocalStorage
gives me false.
This is because rewire copy each method separately creating a new object.
When i change the beforeEach block:
Todo.__set__('globals.localStorage', localStorage);
to
Todo.__set__('localStorage', localStorage);
it throws a referenceError:
1) Todo Model "before each" hook for "have a create method":
ReferenceError: localStorage is not defined
at Function.eval (eval at __set__ (src/store/todo.js:18:26), <anonymous>:1:14)
at Function.__set__ (src/store/todo.js:18:26)
at Context.<anonymous> (spec/index.spec.js:18:5)
How do you test if a service with a global dependency was called?
Share Improve this question edited Nov 13, 2015 at 15:39 Leonid Beschastny 51.5k10 gold badges121 silver badges124 bronze badges asked Oct 22, 2015 at 18:32 NicoNico 1,2612 gold badges13 silver badges29 bronze badges 2- Can you edit your question to include the relevant code from the Todo module? – Jordan Running Commented Oct 22, 2015 at 18:45
- Have the same issue – GN. Commented Jan 13, 2017 at 0:50
1 Answer
Reset to default 5I think the problem is that your Todo module references localStorage
, but you're rewiring global.localStorage
. In the rewire README the example for mocking globals uses the names process
and console
, not globals.process
or globals.console
. Rewire isn't "smart" enough (by design, I assume) to know that they're the same thing. You need to use the exact name that's used in your module. With that in mind, I think what you want is this:
Todo.__set__('localStorage', localStorage);
P.S. In this case I think rewire might be overkill. Since your module's constructor does this.storage = localStorage
, you could just spy on todo.storage
:
todo = new Todo();
sinon.spy(todo.storage, 'setItem');
Edit
I think the ReferenceError happens because, unlike console
and process
in the example, localStorage
doesn't already exist in Node.js. My knowledge of rewire is inplete, however, so there could be another answer.
I think the simplest fix is to just create it in the global context (which also doesn't need rewire):
describe('Todo Model', () => {
beforeEach(function() {
localStorageMock = sinon.mock(new LocalStorage());
global.localStorage = localStorageMock; // <-- set it here
todo = new Todo();
});
afterEach(function() {
delete global.localStorage; // <-- clean up here
});
// ...
});
It's kind of ugly, but it's also dead simple.