Javascript 如何在单个测试的基础上更改模拟实现[Jestjs]

Javascript 如何在单个测试的基础上更改模拟实现[Jestjs],javascript,unit-testing,mocking,jestjs,Javascript,Unit Testing,Mocking,Jestjs,我想在每个单一测试的基础上更改模拟依赖项的实现,方法是扩展默认模拟的行为,并在下一个测试执行时将其恢复到原始实现 更简单地说,这就是我试图实现的目标: 模拟依赖项 在单个测试中更改/扩展模拟实现 在执行下一个测试时恢复到原始模拟 我目前正在使用Jest v21 下面是典型的Jest测试的样子: \uuuu mocks\uuuu/myModule.js const myMockedModule = jest.genMockFromModule('../myModule'); myMockedMo

我想在每个单一测试的基础上更改模拟依赖项的实现,方法是扩展默认模拟的行为,并在下一个测试执行时将其恢复到原始实现

更简单地说,这就是我试图实现的目标:

  • 模拟依赖项
  • 在单个测试中更改/扩展模拟实现
  • 在执行下一个测试时恢复到原始模拟
  • 我目前正在使用
    Jest v21

    下面是典型的Jest测试的样子:

    \uuuu mocks\uuuu/myModule.js

    const myMockedModule = jest.genMockFromModule('../myModule');
    
    myMockedModule.a = jest.fn(() => true);
    myMockedModule.b = jest.fn(() => true);
    
    export default myMockedModule;
    
    import myMockedModule from '../myModule';
    
    // Mock myModule
    jest.mock('../myModule');
    
    beforeEach(() => {
      jest.clearAllMocks();
    });
    
    describe('MyTest', () => {
      it('should test with default mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    
      it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
        // Extend change mock
        myMockedModule.a(); // === true
        myMockedModule.b(); // === 'overridden'
        // Restore mock to original implementation with no side effects
      });
    
      it('should revert back to default myMockedModule mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    });
    
    const myMockedModule = jest.genMockFromModule('../myModule');
    
    let a = true;
    let b = true;
    
    myMockedModule.a = jest.fn(() => a);
    myMockedModule.b = jest.fn(() => b);
    
    myMockedModule.__setA = (value) => { a = value };
    myMockedModule.__setB = (value) => { b = value };
    myMockedModule.__reset = () => {
      a = true;
      b = true;
    };
    export default myMockedModule;
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
      myModule.__setB('overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    
      myModule.__reset();
    });
    
    \uuuu tests\uuuu/myTest.js

    const myMockedModule = jest.genMockFromModule('../myModule');
    
    myMockedModule.a = jest.fn(() => true);
    myMockedModule.b = jest.fn(() => true);
    
    export default myMockedModule;
    
    import myMockedModule from '../myModule';
    
    // Mock myModule
    jest.mock('../myModule');
    
    beforeEach(() => {
      jest.clearAllMocks();
    });
    
    describe('MyTest', () => {
      it('should test with default mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    
      it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
        // Extend change mock
        myMockedModule.a(); // === true
        myMockedModule.b(); // === 'overridden'
        // Restore mock to original implementation with no side effects
      });
    
      it('should revert back to default myMockedModule mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    });
    
    const myMockedModule = jest.genMockFromModule('../myModule');
    
    let a = true;
    let b = true;
    
    myMockedModule.a = jest.fn(() => a);
    myMockedModule.b = jest.fn(() => b);
    
    myMockedModule.__setA = (value) => { a = value };
    myMockedModule.__setB = (value) => { b = value };
    myMockedModule.__reset = () => {
      a = true;
      b = true;
    };
    export default myMockedModule;
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
      myModule.__setB('overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    
      myModule.__reset();
    });
    
    以下是我到目前为止所做的尝试:


    1 - 专业人士

    • 在第一次调用后恢复到原始实现
    缺点

    • 如果测试多次调用
      b
      ,它将中断
    • 在未调用
      b
      之前,它不会恢复到原始实现(在下一个测试中泄漏)
    代码:

    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      myMockedModule.b.mockImplementationOnce(() => 'overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      jest.doMock('../myModule', () => {
        return {
          a: jest.fn(() => true,
          b: jest.fn(() => 'overridden',
        }
      });
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    beforeEach(() => {
      jest.clearAllMocks();
      jest.restoreAllMocks();
    });
    
    // Mock myModule
    jest.mock('../myModule');
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
    
      myMockedModule.a(); // === true
      myMockedModule.b(); // === 'overridden'
    
      // How to get back to original mocked value?
    });
    

    2 - 专业人士

    • 在每个测试中显式地重新模拟
    缺点

    • 无法为所有测试定义默认模拟实现
    • 无法扩展默认实现,强制重新声明每个模拟 方法
    代码:

    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      myMockedModule.b.mockImplementationOnce(() => 'overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      jest.doMock('../myModule', () => {
        return {
          a: jest.fn(() => true,
          b: jest.fn(() => 'overridden',
        }
      });
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    beforeEach(() => {
      jest.clearAllMocks();
      jest.restoreAllMocks();
    });
    
    // Mock myModule
    jest.mock('../myModule');
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
    
      myMockedModule.a(); // === true
      myMockedModule.b(); // === 'overridden'
    
      // How to get back to original mocked value?
    });
    

    3-使用setter方法进行手动模拟(如所述) 专业人士

    • 完全控制模拟结果
    缺点

    • 大量样板代码
    • 难以长期维持
    代码:

    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      myMockedModule.b.mockImplementationOnce(() => 'overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      jest.doMock('../myModule', () => {
        return {
          a: jest.fn(() => true,
          b: jest.fn(() => 'overridden',
        }
      });
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    beforeEach(() => {
      jest.clearAllMocks();
      jest.restoreAllMocks();
    });
    
    // Mock myModule
    jest.mock('../myModule');
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
    
      myMockedModule.a(); // === true
      myMockedModule.b(); // === 'overridden'
    
      // How to get back to original mocked value?
    });
    
    \uuuu mocks\uuuu/myModule.js

    const myMockedModule = jest.genMockFromModule('../myModule');
    
    myMockedModule.a = jest.fn(() => true);
    myMockedModule.b = jest.fn(() => true);
    
    export default myMockedModule;
    
    import myMockedModule from '../myModule';
    
    // Mock myModule
    jest.mock('../myModule');
    
    beforeEach(() => {
      jest.clearAllMocks();
    });
    
    describe('MyTest', () => {
      it('should test with default mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    
      it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
        // Extend change mock
        myMockedModule.a(); // === true
        myMockedModule.b(); // === 'overridden'
        // Restore mock to original implementation with no side effects
      });
    
      it('should revert back to default myMockedModule mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    });
    
    const myMockedModule = jest.genMockFromModule('../myModule');
    
    let a = true;
    let b = true;
    
    myMockedModule.a = jest.fn(() => a);
    myMockedModule.b = jest.fn(() => b);
    
    myMockedModule.__setA = (value) => { a = value };
    myMockedModule.__setB = (value) => { b = value };
    myMockedModule.__reset = () => {
      a = true;
      b = true;
    };
    export default myMockedModule;
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
      myModule.__setB('overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    
      myModule.__reset();
    });
    
    \uuuu tests\uuuu/myTest.js

    const myMockedModule = jest.genMockFromModule('../myModule');
    
    myMockedModule.a = jest.fn(() => true);
    myMockedModule.b = jest.fn(() => true);
    
    export default myMockedModule;
    
    import myMockedModule from '../myModule';
    
    // Mock myModule
    jest.mock('../myModule');
    
    beforeEach(() => {
      jest.clearAllMocks();
    });
    
    describe('MyTest', () => {
      it('should test with default mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    
      it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
        // Extend change mock
        myMockedModule.a(); // === true
        myMockedModule.b(); // === 'overridden'
        // Restore mock to original implementation with no side effects
      });
    
      it('should revert back to default myMockedModule mock', () => {
        myMockedModule.a(); // === true
        myMockedModule.b(); // === true
      });
    });
    
    const myMockedModule = jest.genMockFromModule('../myModule');
    
    let a = true;
    let b = true;
    
    myMockedModule.a = jest.fn(() => a);
    myMockedModule.b = jest.fn(() => b);
    
    myMockedModule.__setA = (value) => { a = value };
    myMockedModule.__setB = (value) => { b = value };
    myMockedModule.__reset = () => {
      a = true;
      b = true;
    };
    export default myMockedModule;
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
      myModule.__setB('overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    
      myModule.__reset();
    });
    

    4 - 缺点

    • 我无法将
      mockImplementation
      恢复为原始模拟返回值,因此会影响下一个测试
    代码:

    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      myMockedModule.b.mockImplementationOnce(() => 'overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      jest.doMock('../myModule', () => {
        return {
          a: jest.fn(() => true,
          b: jest.fn(() => 'overridden',
        }
      });
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    
    beforeEach(() => {
      jest.clearAllMocks();
      jest.restoreAllMocks();
    });
    
    // Mock myModule
    jest.mock('../myModule');
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
    
      myMockedModule.a(); // === true
      myMockedModule.b(); // === 'overridden'
    
      // How to get back to original mocked value?
    });
    

    编写测试的一个好模式是创建一个setupfactory函数,该函数返回测试当前模块所需的数据

    下面是第二个示例之后的一些示例代码,尽管它允许以可重用的方式提供默认值和重写值

    constspyreturns=returnValue=>jest.fn(()=>returnValue);
    描述(“场景”,()=>{
    常量设置=(模拟覆盖)=>{
    常量mockedFunctions={
    答:spyReturns(正确),
    b:spyReturns(正确),
    …模拟覆盖
    }
    返回{
    mockedModule:jest.doMock('../myModule',()=>mockedmfunctions)
    }
    }
    它(“模块a应返回true”,()=>{
    常量{mockedModule}=setup();
    expect(mockedModule.a()).toEqual(true)
    });
    它(“应返回模块a的覆盖”,()=>{
    常量预期值=“覆盖”
    const{mockedModule}=setup({a:spyReturns(预期值)});
    expect(mockedModule.a()).toEqual(预期值)
    });
    });
    
    使用

    import{funcToMock}from./somewhere';
    //从“./某处”导入{(funcToMock as jest.Mock)};//如果使用TypeScript
    开玩笑。嘲弄(“/某处”);
    在每个之前(()=>{
    mockImplementation(()=>{/*默认实现*/});
    });
    test('需要不同funcToMock实现的案例',()=>{
    mockImplementation(()=>{/*特定于此测试的实现*/});
    // ...
    });
    
    派对有点晚,但如果其他人对此有问题

    我们使用TypeScript、ES6和babel进行react本地开发

    我们通常在根目录下模拟外部NPM模块

    我想覆盖aws amplify的Auth类中模块的特定函数,以进行特定测试

    import{Auth}来自“aws放大”;
    从“/GetJwtToken”导入GetJwtToken;
    ...
    它('当idToken应该返回“123”时,异步()=>{
    const spy=jest.spyOn(Auth,'currentSession').mockImplementation(()=>({
    getIdToken:()=>({
    getJwtToken:()=>“123”,
    }),
    }));
    const result=wait GetJwtToken();
    期望(结果)。托比('123');
    spy.mockRestore();
    });
    
    要点:

    教程:

    不错。但是对于像“@private repo/module”这样的npm模块,您如何执行选项2?我看到的大多数例子都有相对路径?这对已安装的模块也有效吗?这是唯一对我有效的方法,用最少的样板文件。在我的场景中,我从一个没有默认导出的包中获得了一个名为TypeScript的导出,因此我最终使用
    import*作为MyModule
    然后
    const{useQuery}=MyModule
    这样我仍然可以以相同的方式使用导入,而不必到处执行
    MyModule.someExport