使用 sinon 在 vue 动作中模拟 class 进行单元测试

Mock class in vue action with sinon for unit testing

我希望能够断言我的 'SIGN_IN' 操作是否按预期工作。

我的操作看起来像这样(不是最干净的实现,但这是我现在必须使用的):

// 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(); 是我想要模拟的响应。 我希望这能够解决我测试中的模拟响应。

AuthService 的 class 如下所示:

// 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;

这是我在测试中尝试的:

// 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);
      });
    });
  });
});

调试时,我无法将 getToken 设为存根响应。它仍然调用在我的操作中创建的实际 userService class 实例:new AuthService(rootState.clientSettings);.

希望您能看到我试图通过测试实现的目标。我只想断言 SET_AUTH_TOKEN 已触发,仅此而已。我怎样才能完全删除这个 AuthService class 以便我的操作使用那个?我真的不想将 AuthService 的实例传递给操作本身,一定有更简洁的方法吗?

你需要的是存根原型方法。

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

参考:how to mock es6 class