Search code examples
javascriptvue.jsunit-testingvuexsinon

Mock class in vue action with sinon for unit testing


I want to be able to assert whether my 'SIGN_IN' action works as intended.

My action looks like this (not the cleanest implementation but it's what I have to work with for now):

// actions.js

import {
  SIGN_IN, SIGN_OUT, SET_AUTH_TOKEN, CLEAR_AUTH_TOKEN, USER_AUTHORISED,
} from '@/store/types';
import AuthService from '../../../authService';

export default {
  async [SIGN_IN]({ commit, rootState }) {
    const authService = new AuthService(rootState.clientSettings);
    let response;
    try {
      response = await authService.getToken();
    } catch (e) {
      console.log('User not authorised to use application.');
    }
    if (response) {
      commit(SET_AUTH_TOKEN, response);
    } else {
      commit(USER_AUTHORISED, false);
    }
  },
  ...
};

authService.getToken(); is what I want to mock the response of. I want this to be able to resolve to a mock response in my test.

The class for AuthService looks like this:

// authService.js

import { PublicClientApplication } from '@azure/msal-browser';

class AuthService {
  constructor(clientSettings) {
    this.config = {
      auth: {
        clientId: clientSettings.clientId,
        authority: `https://login.microsoftonline.com/${clientSettings.tenantId}`,
      },
      cache: {
        cacheLocation: 'sessionStorage',
        storeAuthStateInCookie: false,
      },
    };

    this.scopes = [
      clientSettings.appScope,
    ];

    this.msal = new PublicClientApplication(this.config);
    this.clientSettings = clientSettings;
  }
    ...

    async getToken() {
      const { scopes } = this;
      try {
        const response = await this.msal.acquireTokenSilent({ scopes });
        return response;
      } catch (err) {
        const response = await this.msal.acquireTokenPopup({ scopes });
        return response;
      }
    }

    ...
}

export default AuthService;

This is what I tried in my test:

// actions.spec.js
import sinon, { mock } from 'sinon';
import actions from '@/store/modules/auth/actions';
import { SIGN_IN, SIGN_OUT } from '@/store/types';
import { PublicClientApplication } from '@azure/msal-browser';
import AuthService from '../../../../../src/authService';
import templateRootState from '../../../../helpers/rootState.json';

describe('Auth', () => {
  describe('Actions', () => {
    let state;

    beforeEach(() => {
      state = JSON.parse(JSON.stringify(templateRootState));
    });

    afterEach(() => {
      sinon.reset();
    });

    describe('SIGN_IN', () => {
      it.only('returns a token from the AuthService & sets it in the store', async () => {
        const commit = sinon.spy();
        const mockResponse = {
          token: 'bobbins'
        };

        sinon.stub(AuthService, 'getToken').resolves(mockResponse);

        const requestParams = { commit, rootState: state };

        await actions[SIGN_IN](requestParams);

        expect(commit.calledOnce).toEqual(true);
        expect(commit.args.length).toEqual(1);
        expect(commit.args[0][0]).toEqual('SET_AUTH_TOKEN');
        expect(commit.args[0][1]).toEqual(mockResponse);
      });
    });
  });
});

While debugging, I am unable to get getToken to be a stubbed response. It still calls the actual userService class instance created in my action: new AuthService(rootState.clientSettings);.

Hopefully you can see what I'm trying to achieve with my test. I just want to assert that SET_AUTH_TOKEN is triggered and nothing else. How can I stub out this AuthService class entirely so that my action uses that one instead? I don't really want to be passing an instance of AuthService into the action itself, there must be a neater way of doing it?


Solution

  • What you need is to stub prototype method.

    sinon.stub(AuthService.prototype, 'getToken').resolves(mockResponse);
    

    Reference: how to mock es6 class