最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Injecting global variables while testing copy the actual object, need the same instance - Stack Overflow

programmeradmin1浏览0评论

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
Add a ment  | 

1 Answer 1

Reset to default 5

I 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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论