wake-up-neo.net

Wie man Funktionen im selben Modul mit Jest simuliert

Was ist der beste Weg, um das folgende Beispiel richtig zu verspotten?

Das Problem ist, dass nach dem Importieren foo den Verweis auf die nicht gedockte ursprüngliche bar behält.

module.js:

export function bar () {
    return 'bar';
}

export function foo () {
    return `I am foo. bar is ${bar()}`;
}

module.test.js:

import * as module from '../src/module';

describe('module', () => {
    let barSpy;

    beforeEach(() => {
        barSpy = jest.spyOn(
            module,
            'bar'
        ).mockImplementation(jest.fn());
    });


    afterEach(() => {
        barSpy.mockRestore();
    });

    it('foo', () => {
        console.log(jest.isMockFunction(module.bar)); // outputs true

        module.bar.mockReturnValue('fake bar');

        console.log(module.bar()); // outputs 'fake bar';

        expect(module.foo()).toEqual('I am foo. bar is fake bar');
        /**
         * does not work! we get the following:
         *
         *  Expected value to equal:
         *    "I am foo. bar is fake bar"
         *  Received:
         *    "I am foo. bar is bar"
         */
    });
});

Vielen Dank!

EDIT: Ich könnte mich ändern:

export function foo () {
    return `I am foo. bar is ${bar()}`;
}

zu 

export function foo () {
    return `I am foo. bar is ${exports.bar()}`;
}

aber das ist p. hässlich meiner Meinung nach überall zu tun: /

33
Mark

die Lösung, für die ich mich entschieden habe, war die Verwendung von Dependency Injection , indem ein Standardargument gesetzt wurde.

Also würde ich mich ändern

export function bar () {
    return 'bar';
}

export function foo () {
    return `I am foo. bar is ${bar()}`;
}

zu

export function bar () {
    return 'bar';
}

export function foo (_bar = bar) {
    return `I am foo. bar is ${_bar()}`;
}

Dies ist keine grundlegende Änderung an der API meiner Komponente, und ich kann die Leiste in meinem Test leicht überschreiben, indem ich Folgendes mache

import { foo, bar } from '../src/module';

describe('module', () => {
    it('foo', () => {
        const dummyBar = jest.fn().mockReturnValue('fake bar');
        expect(foo(dummyBar)).toEqual('I am foo. bar is fake bar');
    });
});

Dies hat den Vorteil, dass es auch zu etwas schönerem Testcode kommt :)

4
Mark

Das Problem scheint darauf zurückzuführen zu sein, wie Sie erwarten, dass der Umfang des Balkens gelöst wird. 

Einerseits exportieren Sie in module.js zwei Funktionen (anstelle eines Objekts, das diese beiden Funktionen enthält). Aufgrund der Art und Weise, wie Module exportiert werden, ist der Verweis auf den Container der exportierten Dinge exports, wie Sie es erwähnt haben. 

Auf der anderen Seite behandeln Sie Ihren Export (den Sie als Alias ​​module bezeichnet haben) wie ein Objekt, das diese Funktionen enthält und versucht, eine seiner Funktionen (die Funktionsleiste) zu ersetzen.

Wenn Sie sich Ihre Foo-Implementierung genau ansehen, halten Sie tatsächlich einen festen Bezug zur Bar-Funktion.

Wenn Sie der Meinung sind, dass Sie die Bar-Funktion durch eine neue ersetzt haben, haben Sie die Referenzkopie im Rahmen Ihres Moduls ersetzt.test.js

Um foo tatsächlich eine andere Version von bar verwenden zu lassen, haben Sie zwei Möglichkeiten: 

  1. Exportieren Sie in module.js eine Klasse oder eine Instanz, die sowohl die Methode foo als auch die Methode bar enthält: 

    Module.js: 

    export class MyModule {
      function bar () {
        return 'bar';
      }
    
      function foo () {
        return `I am foo. bar is ${this.bar()}`;
      }
    }
    

    Beachten Sie die Verwendung des this - Schlüsselworts in der foo-Methode. 

    Module.test.js: 

    import { MyModule } from '../src/module'
    
    describe('MyModule', () => {
      //System under test :
      const sut:MyModule = new MyModule();
    
      let barSpy;
    
      beforeEach(() => {
          barSpy = jest.spyOn(
              sut,
              'bar'
          ).mockImplementation(jest.fn());
      });
    
    
      afterEach(() => {
          barSpy.mockRestore();
      });
    
      it('foo', () => {
          sut.bar.mockReturnValue('fake bar');
          expect(sut.foo()).toEqual('I am foo. bar is fake bar');
      });
    });
    
  2. Schreiben Sie, wie Sie gesagt haben, die globale Referenz in den globalen Container exports. Dies ist keine empfohlene Vorgehensweise, da Sie möglicherweise seltsame Verhaltensweisen in andere Tests einführen, wenn Sie die Exporte nicht ordnungsgemäß in den Ausgangszustand zurücksetzen.

12
John-Philip

Eine alternative Lösung kann sein, das Modul in eine eigene Codedatei zu importieren und die importierte Instanz aller exportierten Entitäten zu verwenden. So was:

import * as thisModule from './module';

export function bar () {
    return 'bar';
}

export function foo () {
    return `I am foo. bar is ${thisModule.bar()}`;
}

Das Spotteln von bar ist jetzt wirklich einfach, da foo auch die exportierte Instanz von bar verwendet:

import * as module from '../src/module';

describe('module', () => {
    it('foo', () => {
        spyOn(module, 'bar').and.returnValue('fake bar');
        expect(module.foo()).toEqual('I am foo. bar is fake bar');
    });
});

Das Importieren des Moduls in seinen eigenen Code sieht seltsam aus, aber aufgrund der Unterstützung von ES6 für zyklische Importe funktioniert es wirklich reibungslos.

9
MostafaR

Wenn Sie Ihre Exporte definieren, können Sie Ihre Funktionen als Teil des Exportobjekts referenzieren. Dann können Sie die Funktionen in Ihren Mocks einzeln überschreiben. Dies hängt damit zusammen, wie der Import als Referenz und nicht als Kopie funktioniert. 

module.js:

exports.bar () => {
    return 'bar';
}

exports.foo () => {
    return `I am foo. bar is ${exports.bar()}`;
}

module.test.js:

describe('MyModule', () => {

  it('foo', () => {
    let module = require('./module')
    module.bar = jest.fn(()=>{return 'fake bar'})

    expect(module.foo()).toEqual('I am foo. bar is fake bar');
  });

})
1
Sean

Ich hatte das gleiche Problem, und aufgrund der Linting-Standards des Projekts war das Definieren einer Klasse oder das Umschreiben von Referenzen in der Variablen exports keine durch Code überprüfbare Option, auch wenn dies durch die Flintendefinitionen nicht verhindert wird. Was ich als praktikable Option gefunden habe, ist die Verwendung des babel-rewire-plugin , das zumindest im Aussehen viel sauberer ist. Während ich fand, dass dies in einem anderen Projekt verwendet wurde, auf das ich Zugriff hatte, fiel mir auf, dass es sich bereits in einer ähnlichen Frage befand, die ich hier verlinkt habe. Dies ist ein für diese Frage angepasster Ausschnitt (und ohne Verwendung von Spionen), der in der verknüpften Antwort als Referenz bereitgestellt wird (ich habe zusätzlich zum Entfernen von Spionen auch Semikolons hinzugefügt, da ich kein Heiden bin): 

import __RewireAPI__, * as module from '../module';

describe('foo', () => {
  it('calls bar', () => {
    const barMock = jest.fn();
    __RewireAPI__.__Rewire__('bar', barMock);
    
    module.foo();

    expect(bar).toHaveBeenCalledTimes(1);
  });
});

https://stackoverflow.com/a/45645229/6867420

0
Brandon Hunter

Funktioniert bei mir:

cat moduleWithFunc.ts

export function funcA() {
 return export.funcB();
}
export function funcB() {
 return false;
}

cat moduleWithFunc.test.ts

import * as module from './moduleWithFunc';

describe('testFunc', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  afterEach(() => {
    module.funcB.mockRestore();
  });

  it.only('testCase', () => {
    // arrange
    jest.spyOn(module, 'funcB').mockImplementationOnce(jest.fn().mockReturnValue(true));

    // act
    const result = module.funcA();

    // assert
    expect(result).toEqual(true);
    expect(module.funcB).toHaveBeenCalledTimes(1);
  });
});