在我的单元测试用例中模拟我的 ActionContext 的正确方法是什么?

What is the correct way for Mock my ActionContext in my unit test case?

我在单元测试中模拟我的 ActionContext 时遇到了问题。实际上,我以这种方式模拟了我的 ActionContext :

testUtilities.ts

import { ActionContext, Dispatch, Commit } from 'vuex';
import { IRootState } from './../src/store/store';
import { expect } from 'chai';
import { DispatchAccessorNoPayload } from 'vuex-typescript';

export interface ContextTest {
    state: any;
    rootState: IRootState;
    getters: any;
    rootGetters: any;
}

type TestActionTypes = (action: DispatchAccessorNoPayload<any, IRootState, any>,
    {
        state,
        rootState,
        getters,
        rootGetters,
    }: ContextTest,
    expectedMutations: any,
    done: Mocha.Done) => void;

export const testAction: TestActionTypes = (
    action,
    {
        state,
        rootState,
        getters,
        rootGetters,
    },
    expectedMutations,
    done
) => {
    let countDispatch = 0;
    let countCommit = 0;

    // mock dispatch
    const dispatch: Dispatch = (type: string) => {
        return new Promise((resolve, reject) => {
            const mutation = expectedMutations[countDispatch];
            try {
                expect(mutation.type).to.equal(type);
            } catch (error) {
                done(error);
                reject(error);
            }

            countDispatch++;

            if (countDispatch >= expectedMutations.length) {
                done();
                resolve();
            }
        });
    };

    // mock commit
    const commit: Commit = (type: string) => {
        const mutation = expectedMutations[countCommit];
        try {
            expect(mutation.type).to.equal(type);
        } catch (error) {
            done(error);
        }
        countCommit++;
        if (countCommit >= expectedMutations.length) {
            done();
        }
    };

    const context: ActionContext<any, IRootState> = {
        state,
        rootState,
        rootGetters,
        commit,
        dispatch,
        getters,
    };

    // call the action with mocked store and arguments
    action(context);

    // check if no mutations should have been dispatched
    if (expectedMutations.length === 0) {
        expect(countCommit).to.equal(0);
        expect(countDispatch).to.equal(0);
        done();
    }
};

我使用 Vuex 文档中的这个助手:https://vuex.vuejs.org/guide/testing.html#testing-actions

customer.module.ts

import { IRootState } from "./../store";
import { ICustomer } from "./../../models/customer.model";
import { ActionContext } from "vuex";
import { getStoreAccessors } from "vuex-typescript";
import { CustomerService } from './../../services/entities/customer.service';

export interface ICustomersState {
  customers: ICustomer[];
}

const initialState: ICustomersState = {
  customers: []
};

type CustomersContext = ActionContext<ICustomersState, IRootState>;

export const mutations = {
  FETCH_CUSTOMERS(state: ICustomersState, customers: ICustomer[]) {
    state.customers = customers;
  }
};

export const actions = {
  GETALL_CUSTOMERS(context: CustomersContext) {
    let customerService = new CustomerService();
    return customerService.getAll().then(customers => {
      context.commit("FETCH_CUSTOMERS", customers);
    });
  },
}

export const customer = {
  namespaced: true,
  state: { ...initialState },
  mutations,
  actions
};

const { read, commit, dispatch } = getStoreAccessors<ICustomersState, IRootState>("customers");

export const getAllCustomersDispatch = dispatch(customer.actions.GETALL_CUSTOMERS);

customer.actions.spec.ts

import { getAllCustomersDispatch } from './../../../../src/store/modules/customer';
import { testAction, ContextTest } from "./../../../../tests/testUtilities";

describe("Customer Actions", () => {
    it("GETALL_CUSTOMERS", (done) => {
        const context: ContextTest = {
            state: [],
            getters: {},
            rootGetters: {},
            rootState: {
                customers: {
                    customers: []
                }
            }
        }
        testAction(getAllCustomersDispatch, context, [{
            type: 'customers/FETCH_CUSTOMERS'
        }], done)
    });
});

预期结果

我希望我的单元测试验证操作 GETALL_CUSTOMERS 是否正确调用我的提交 FETCH_CUSTOMERS

测试输出

+ expected - actual

-"customers/FETCH_CUSTOMERS"
+"customers/GETALL_CUSTOMERS"

我在我的 testAction 函数中的 commit 常量内放置了一个断点,但是我的处理没有通过这个。

我解决了我的问题,我使用 sinon.js 来模拟我的 API 调用并模拟我的 commit

我得到了预期的结果

customer.actions.spec.ts

import { actions } from "./../../../../src/store/modules/customer";
import { spy, createSandbox } from "sinon";
import { expect } from "chai";
import Axios from "axios";
import Sinon = require("sinon");
import { ICustomer } from "src/models/customer.model";

describe("Customer Actions", () => {
  let sandbox: Sinon.SinonSandbox;
  beforeEach(() => (sandbox = createSandbox()));

  afterEach(() => sandbox.restore());

  it("GETALL_CUSTOMERS", async () => {
    // Assign
    let data: ICustomer[] = [
      {
        id: 1,
        address: "",
        company: "",
        firstName: "",
        lastName: "",
        zipcode: "",
        siret: "",
        tel: "",
        projects: []
      }
    ];
    const resolved = new Promise<any>(r => r({ data }));
    sandbox.stub(Axios, "get").returns(resolved);

    let commit = spy();
    let state = {
      customers: []
    };
    const getters: {} = {};
    let rootGetters: {} = {};
    let rootState: {
      customers: {
        customers: [];
      };
    } = {
      customers: {
        customers: []
      }
    };
    // Act
    await actions.GETALL_CUSTOMERS({
      commit,
      state,
      dispatch: () => Promise.resolve(),
      getters,
      rootGetters,
      rootState
    });
    // Assert
    expect(commit.args).to.deep.equal([
      ["FETCH_CUSTOMERS_REQUEST"],
      ["FETCH_CUSTOMERS_SUCCESS", data]
    ]);
})

customer.module.ts

import { IRootState } from "./../store";
import { ICustomer } from "./../../models/customer.model";
import { ActionContext } from "vuex";
import { getStoreAccessors } from "vuex-typescript";
import { CustomerService } from './../../services/entities/customer.service';

export interface ICustomersState {
  customers: ICustomer[];
}

const initialState: ICustomersState = {
  customers: []
};

type CustomersContext = ActionContext<ICustomersState, IRootState>;

export const mutations = {
  FETCH_CUSTOMERS(state: ICustomersState, customers: ICustomer[]) {
    state.customers = customers;
  }
};

export const actions = {
  async GETALL_CUSTOMERS(context: CustomersContext) {
    let customerService = new CustomerService();
    context.commit("FETCH_CUSTOMERS_REQUEST", customers);
    await customerService.getAll().then(customers => {
      context.commit("FETCH_CUSTOMERS_SUCCESS", customers);
    }).catch(err => context.commit("FETCH_CUSTOMERS_ERROR", err));
  },
}

export const customer = {
  namespaced: true,
  state: { ...initialState },
  mutations,
  actions
};

const { read, commit, dispatch } = getStoreAccessors<ICustomersState, IRootState>("customers");

export const getAllCustomersDispatch = dispatch(customer.actions.GETALL_CUSTOMERS);

我删除了文件 testUtilities.ts 因为我不再需要它了。