如何模拟ES6超类并使用jest.js进行监视?

时间:2019-08-12 08:48:53

标签: unit-testing ecmascript-6 jestjs

我有一个类Qux,它是从类Baa继承的,我想在测试Qux时模拟Baa。如果我不尝试监视模拟BaaMock,则原则上可行。

如果我想监视模拟的类,doc说我应该使用jest.fn()而不是类。但是,这似乎无法正常工作:继承类Qux的某些方法丢失了。

一些示例代码(也可以从https://github.com/stefaneidelloth/testDemoES6Jest获得):

超级类Baa(/src/baa.js):

import Foo from './foo.js';

export default class Baa extends Foo {

    constructor(name){
        super(name);    
    }

    baaMethod(){
        return 'baaMethod';
    }

    overridableMethod() {
        return 'baa';
    }
}

继承类Qux(/src/qux.js):

import Baa from './baa.js';

export default class Qux extends Baa {

    constructor(name){
        super(name);        
    }

    quxMethod(){
        return 'quxMethod';
    }

    overridableMethod() {
        return 'qux';
    }
}

A。测试继承类Qux的可能性(/test/qux.test.js):

jest.mock('./../src/baa.js', () => {
    return class BaaMock {
        constructor(name){
            this.name = name;
        }

        baaMethod(){
            return 'baaMockedMethod';
        }
    }   
});

import Qux from './../src/qux.js';

describe('Qux', function(){

    var sut;        

    beforeEach(function(){          
        sut = new Qux('qux');       
    });

    it('quxMethod', function(){         
        expect(sut.quxMethod()).toEqual('quxMethod');
    }); 

    it('baaMethod', function(){         
        expect(sut.baaMethod()).toEqual('baaMockedMethod');
    }); 

    it('overridableMethod', function(){         
        expect(sut.overridableMethod()).toEqual('qux');
    });         

}); 

B。。为了能够监视模拟的类,我尝试用模拟函数替换该类(另请参见https://jestjs.io/docs/en/es6-class-mocks):

import Baa from './../src/baa.js';
jest.mock('./../src/baa.js', 
    function(){
        return jest.fn().mockImplementation(
            function(name){
                return {
                    name:name,
                    baaMethod: () =>{ return 'baaMockedMethod';}
                };
            }
        );
    }
);

import Qux from './../src/qux.js';

describe('Qux', function(){

    var sut;        

    beforeEach(function(){

        //Baa.mockClear();
        sut = new Qux('qux');           
        //expect(Baa).toHaveBeenCalledTimes(1);
    });

    it('quxMethod', function(){         
        expect(sut.quxMethod()).toEqual('quxMethod');
    }); 

    it('baaMethod', function(){         
        expect(sut.baaMethod()).toEqual('baaMockedMethod');
    }); 

    it('overridableMethod', function(){         
        expect(sut.overridableMethod()).toEqual('qux');
    });         

}); 

结果,测试失败并出现以下错误

FAIL test/qux.test.js

  Qux
    × quxMethod (7ms)
    √ baaMethod (4ms)
    × overridableMethod (2ms)

  ● Qux › quxMethod

    TypeError: sut.quxMethod is not a function

      28 | 
      29 |  it('quxMethod', function(){         
    > 30 |      expect(sut.quxMethod()).toEqual('quxMethod');
         |                 ^
      31 |  }); 
      32 | 
      33 |  it('baaMethod', function(){         

      at Object.quxMethod (test/qux.test.js:30:14)

  ● Qux › overridableMethod

    TypeError: sut.overridableMethod is not a function

      36 | 
      37 |  it('overridableMethod', function(){         
    > 38 |      expect(sut.overridableMethod()).toEqual('qux');
         |                 ^
      39 |  });         
      40 | 
      41 | });  

      at Object.overridableMethod (test/qux.test.js:38:14)

我希望sut的实例Qux仍包含类Qux定义的方法quxMethodoverridableMethod

=> 这是个玩笑吗?

=> 如果没有,为什么我需要在Baa的模拟中实现Qux的所有方法!???

=> 我需要如何适应示例代码 B 才能成功模拟Baa类,以便Qux仍然能够继承它?

3 个答案:

答案 0 :(得分:1)

Modify this 将覆盖原来的方法。对于您的示例,如果我想模拟 overridableMethod,我会修改 overridableMethodthis

jest.mock('../../src/baa.js',
    function(){
        return jest.fn().mockImplementation(
            function(name){
                this.name=name;
                this.baaMethod = ()=>{
                    return 'baaMockedMethod';
                };

                // mock overridable method
                this.overridableMethod = ()=>{
                    return 'mock'
                }

                return this;
            }
        );
    }
);

这使得测试错误:

● Qux › overridableMethod

    expect(received).toEqual(exptected) // deep equality

    Expected: "qux"
    Received: "mock"

      51 |
      52 |   it("overridableMethod", function () {
    >          expect(sut.overridableMethod()).toEqual("qux");
         |                                     ^
      54 |   });
      55 | });
      56 |

      at Object.<anonymous> (tests/unit/qux.spec.js:53:37)

你可以使用类模拟来解决这个问题:

const mockConstructor = jest.fn().mockImplementation(function(name){this.name = name})
const mockOverridableMethod = jest.fn(() => 'mock')
const mockBaaMethod = jest.fn(() => 'baaMockedMethod')

jest.mock("../../src/baa.js", () => {
  return class BaaMock {
    constructor(name) {
      return (mockConstructor.bind(this))(name)
    }

    overridableMethod() {
      return mockOverridableMethod()
    }

    baaMethod() {
      return mockBaaMethod()
    }
  };
});

import Qux from "../../src/qux.js";

describe("Qux", function () {
  var sut;

  beforeEach(function () {
    mockConstructor.mockClear()
    sut = new Qux("qux");
    expect(mockConstructor).toHaveBeenCalledTimes(1); // use mockConstructor check if have been called
  });

  it("quxMethod", function () {
    expect(sut.quxMethod()).toEqual("quxMethod");
  });

  it("baaMethod", function () {
    expect(sut.baaMethod()).toEqual("baaMockedMethod");
  });

  it("overridableMethod", function () {
    expect(sut.overridableMethod()).toEqual("qux");
  });
});

对包括构造函数在内的每个函数都使用 jest.fn() 让我们可以窥探这些函数。

答案 1 :(得分:0)

我相信您不应该那样做。我没有几个避免这种方法的强烈理由:

  1. 是的,您将必须列出模拟中的所有方法。更重要的是:对于某些方法,您将必须提供实际的实现(如果某个方法应返回布尔值而其他方法依赖于结果该怎么办?如果某个方法返回在其他方法中用于某些计算的数字该怎么办?)。这很难实现,很难维护,在重构时很容易中断。
  2. 您实际上测试了实施细节。这永远不会结束。如果有一天您想切换到其他父类,或者只是将两个类合并为一个类,您肯定需要更新测试。即使系统仍然可以正常工作。因此,您需要做一些没有任何价值的附加工作-只是为了保持测试通过。值得吗?
  3. 模拟超级类实际上意味着,如果系统运行良好,您将失去信心。对子类模拟测试说一些超级方法,模拟与实际实现有所不同。您的测试通过了,但是实际系统失败了。

总结以上所有内容,我建议您不惜一切代价模拟超类。

答案 2 :(得分:0)

mockImplementation的功能内,可以使用上下文this。以下代码有效:

import Baa from './../src/baa.js';
jest.mock('./../src/baa.js', 
    function(){
        return jest.fn().mockImplementation(
            function(name){
                this.name=name;
                this.baaMethod = ()=>{
                    return 'baaMockedMethod';
                };
                return this;                
            }
        );
    }
);

import Qux from './../src/qux.js';

describe('Qux', function(){

    var sut;        

    beforeEach(function(){

        Baa.mockClear();
        sut = new Qux('qux');           
        expect(Baa).toHaveBeenCalledTimes(1);
    });

    it('quxMethod', function(){         
        expect(sut.quxMethod()).toEqual('quxMethod');
    }); 

    it('baaMethod', function(){         
        expect(sut.baaMethod()).toEqual('baaMockedMethod');
    }); 

    it('overridableMethod', function(){         
        expect(sut.overridableMethod()).toEqual('qux');
    });         

});