web-dev-qa-db-fra.com

Jest: Comment se moquer d'une méthode spécifique d'une classe

Supposons que j'ai le cours suivant: 

export default class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }
    sayMyName() {
        console.log(this.first + " " + this.last);
    }
    bla() {
        return "bla";
    }
}

Supposons que je veuille créer une classe fausse où la méthode 'sayMyName' sera fausse et la méthode 'bla' restera telle quelle.

Le test que j'ai écrit est: 

const Person = require("../Person");

jest.mock('../Person', () => {
    return jest.fn().mockImplementation(() => {
        return {sayMyName: () => {
            return 'Hello'
        }};
    });
});


let person = new Person();
test('MyTest', () => {
    expect(person.sayMyName()).toBe("Hello");
    expect(person.bla()).toBe("bla");
})

La première instruction 'expect' passe, ce qui signifie que 'sayMyName' a été moqué avec succès. Mais le second 'expect' échoue avec l'erreur: 

TypeError: person.bla n'est pas une fonction

Je comprends que la classe simulée a effacé toutes les méthodes ..__ Je veux savoir comment se moquer d’une classe de telle sorte que seules des méthodes spécifiques soient simulées.

5
CrazySynthax

Je ne vois pas comment l'implémentation simulée résoudrait quelque chose pour vous. Je pense que cela fait un peu plus de sens

import Person from "./Person";

describe("Person", () => {
  it("should...", () => {
    const sayMyName = Person.prototype.sayMyName = jest.fn();
    const person = new Person('guy', 'smiley');
    const expected = {
      first: 'guy',
      last: 'smiley'
    }

    person.sayMyName();

    expect(sayMyName).toHaveBeenCalledTimes(1);
    expect(person).toEqual(expected);
  });
});
7
sesamechicken

Ont posé une question similaire et je pense trouvé une solution. Cela devrait fonctionner peu importe où l'instance de la classe Person est réellement utilisée.

const Person = require("../Person");

jest.mock("../Person", function () {
    const { default: mockRealPerson } = jest.requireActual('../Person');

    mockRealPerson.prototype.sayMyName = function () {
        return "Hello";
    }    

    return mockRealPerson
});

test('MyTest', () => {
    const person = new Person();
    expect(person.sayMyName()).toBe("Hello");
    expect(person.bla()).toBe("bla");
});
2
madhamster

plutôt que de se moquer de la classe, vous pouvez l'étendre comme ceci:

class MockedPerson extends Person {
  sayMyName () {
    return 'Hello'
  }
}
// and then
let person = new MockedPerson();
2
Billy Reilly

Si vous utilisez TypeScript, vous pouvez effectuer les opérations suivantes:

Person.prototype.sayMyName = jest.fn().mockImplementationOnce(async () => 
        await 'my name is dev'
);

Et dans votre test, vous pouvez faire quelque chose comme ça:

const person = new Person();
const res = await person.sayMyName();
expect(res).toEqual('my name is dev');

J'espère que cela aide quelqu'un!

2
Saif Asad

J'ai combiné les réponses @sesamechicken et @Billy Reilly pour créer une fonction util qui simule (une ou plusieurs) méthodes spécifiques d'une classe, sans impacter de manière définitive la classe elle-même.

/**
* @CrazySynthax class, a tiny bit updated to be able to easily test the mock.
*/
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    sayMyName() {
        return this.first + " " + this.last + this.yourGodDamnRight();
    }

    yourGodDamnRight() {
        return ", you're god damn right";
    }
}

/**
 * Return a new class, with some specific methods mocked.
 *
 * We have to create a new class in order to avoid altering the prototype of the class itself, which would
 * most likely impact other tests.
 *
 * @param Klass: The class to mock
 * @param functionNames: A string or a list of functions names to mock.
 * @returns {Class} a new class.
 */
export function mockSpecificMethods(Klass, functionNames) {
    if (!Array.isArray(functionNames))
        functionNames = [functionNames];

    class MockedKlass extends Klass {
    }

    const functionNamesLenght = functionNames.length;
    for (let index = 0; index < functionNamesLenght; ++index) {
        let name = functionNames[index];
        MockedKlass.prototype[name] = jest.fn();
    };

    return MockedKlass;
}

/**
* Making sure it works
*/
describe('Specific Mocked function', () => {
    it('mocking sayMyName', () => {
        const walter = new (mockSpecificMethods(Person, 'yourGodDamnRight'))('walter', 'white');

        walter.yourGodDamnRight.mockReturnValue(", that's correct"); // yourGodDamnRight is now a classic jest mock;

        expect(walter.sayMyName()).toBe("walter white, that's correct");
        expect(walter.yourGodDamnRight.mock.calls.length).toBe(1);

        // assert that Person is not impacted.
        const saul = new Person('saul', 'goodman');
        expect(saul.sayMyName()).toBe("saul goodman, you're god damn right");
    });
});
0
JolanC