1

Problem

I'm testing a custom redux middleware using Jest and SinonJS and more precisely I want to test if some functions are called on special conditions inside the middleware.

I use SinonJS for creating the spies and I run my tests with Jest. I initialised the spies for the specific functions I want to track and when I check if the spies has been called, the spies has not been even if it should be (manually tested).

Code

Here is the middleware I want to test :

import { Cookies } from 'react-cookie';
import setAuthorizationToken from './setAuthorizationToken';

let cookies = new Cookies();

export const bindTokenWithApp = (store) => (next) => (action) => {
    // Select the token before action
    const previousToken = getToken(store.getState());
    // Dispatch action
    const result = next(action);
    // Select the token after dispatched action
    const nextToken = getToken(store.getState());

    if (previousToken !== nextToken) {
        if (nextToken === '') {
            setAuthorizationToken(false);
            cookies.remove(SESSION_COOKIE_NAME, COOKIE_OPTIONS);
        } else {
            cookies.set(SESSION_COOKIE_NAME, nextToken, COOKIE_OPTIONS);
            setAuthorizationToken(nextToken);
        }
    }

    return result;
};

Here is my actual test

import { bindTokenWithApp } from './middleware';
import { Cookies } from 'react-cookie';
import sinon, { assert } from 'sinon';
import setAuthorizationToken from './setAuthorizationToken';

describe('bindTokenWithApp', () => {
    const next = jest.fn();
    const action = jest.fn();
    let cookies = new Cookies();

    it('removes cookies when there is no token', () => {
        // My actual not working spies
        const cookieSpy = sinon.spy(cookies.remove);
        const authSpy = sinon.spy(setAuthorizationToken);

        // Stub for the specific case. This code works, 
        // I console.logged in the middleware and I'm getting the below values
        const getState = sinon.stub();
        getState.onFirstCall().returns({ auth: { token: 'a token' } });
        getState.onSecondCall().returns({ auth: { token: '' } });
        const store = { getState: getState };

        bindTokenWithApp(store)(next)(action);

        assert.calledOnce(cookieSpy);
        assert.calledOnce(authSpy);
        // Output : AssertError: expected remove to be called once but was called 0 times
        // AssertError: expected setAuthorizationToken to be called once but was called 0 times

        cookieSpy.restore(); // <= This one works
        authSpy.restore(); // TypeError: authSpy.restore is not a function 

  });
});

I've read SinonJS doc and a few StackOverFlow posts but without solutions. I also can't call authSpy.restore();. I think I do not initialise spies the right way and I'm misunderstanding a concept in SinonJS but I can't find which one !

The setAuthorizationToken signature is
(alias) const setAuthorizationToken: (token: any) => void
import setAuthorizationToken

I think it's a classical module so I can't figure out why I struggle with authSpy.restore();

Tenshock
  • 107
  • 10

1 Answers1

0

The two spies you have actually have two different fixes, both with the same underlying problem. sinon.spy(someFunction) doesn't actually wrap someFunction itself, it returns a spy for it but doesn't perform any replacement.

For the first spy, there exists a shorthand to automatically wrap an object method: sinon.spy(cookie, 'remove') should do what you need.

For the second spy, it is more complicated as you need to wrap the spy around the default export of setAuthorizationToken. For that you will need something like proxyquire. Proxyquire is a specialized require mechanism that allows you to replace imports with your desired test methods. Here's a brief of what you'll need to do:

const authSpy = sinon.spy(setAuthorizationToken);
bindTokenWithApp = proxyquire('./middleware', { './setAuthorizationToken': authSpy});
Michael Pratt
  • 3,438
  • 1
  • 17
  • 31
  • Thanks for the explanation. I've tried the two solutions but none works. I understand the underlying problem, for the first spy I have now in my test : `sinon.spy(cookies, 'remove'); assert.calledOnce(cookies.remove);` but with the same output. For the second spy, I've tried with proxyquire and jest.mock (from this [article](https://madole.xyz/mocking-relative-dependencies-in-jest-with-jest-mock/) but without success). I can't assert `bindTokenWithApp = proxyquire…` because it's read-only from import. And when I assert `const bindTokenWithApp =…` I can't execute it because it's not a function… – Tenshock Feb 13 '19 at 18:24