Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock ES6 super class and spy on it with jest.js?

I have a class Qux that inherits from class Baa and I would like to mock Baa while testing Qux. That works in principle if I don't try to spy on the mock BaaMock.

If I would like to spy on the mocked class, the doc says that I should use a jest.fn() instead of the class. However, that does not seem to work correctly: some of the methods of the inheriting class Qux are lost.

Some example code (also available at https://github.com/stefaneidelloth/testDemoES6Jest) :

Super class 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';
    }
}

Inheriting class 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. Test for inheriting class Qux without a possibility to spy (/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. In order to be able to spy on the mocked class, I tried to replace the class with a mock function (also see 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');
    });         

}); 

As a result, the test fails with following errors:

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)

I would expect my instance sut of Qux to still contain the methods quxMethod and overridableMethod that are defined by the class Qux.

=> Is this a bug of jest?

=> If not, why should do I need to implement all methods of Qux in the mock for Baa !!???

=> How do I need to adapt my example code B do successfully mock the class Baa, so that Qux is still able to inherit from it?

like image 471
Stefan Avatar asked Aug 12 '19 08:08

Stefan


People also ask

How do you mock an ES6 class Jest?

Jest can be used to mock ES6 classes that are imported into files you want to test. ES6 classes are constructor functions with some syntactic sugar. Therefore, any mock for an ES6 class must be a function or an actual ES6 class (which is, again, another function). So you can mock them using mock functions.

How do you use spyOn function in Jest?

To spy on an exported function in jest, you need to import all named exports and provide that object to the jest. spyOn function. That would look like this: import * as moduleApi from '@module/api'; // Somewhere in your test case or test suite jest.

How do you mock an interface in Jest?

To mock a TypeScript interface with Jest, we just need to create objects that match the shape of the interface. The mocked function takes 2 numbers as arguments and returns a number, and we have the same signature and return type in the mocked function. So no error will be raised.


1 Answers

I believe you should not do that way. I see few strong reasons avoiding such an approach:

  1. Yes, you will have to list all the methods in your mock. Even more: for some methods you will have to provide actual implementation(what if some method should return boolean and other method rely on result? what if some method returns number that is used for some calculation in other method?). It's hard to achieve, it's hard to maintain, it breaks really easily on refactoring.
  2. You actually test implementation details. And this never ends well. What if some day you'd like to switch to different parent class or just merge 2 classes in one - you'd definitely need to update test. Even when system still works fine. So you need to do additional work without any value - just to keep tests passing. Is it worth that?
  3. Mocking super class actually means you would be less confident if system works well. Say test for child class mocks some super method and mock differs from actual implementation. Your tests are passing but real system fails.

Summarizing all above I propose you avoid mocking super class at any cost.

like image 53
skyboyer Avatar answered Sep 27 '22 00:09

skyboyer