Wie man eine Methode des Jasmin-Scheinobjekts stubbt?

76

Gemäß der Jasmine-Dokumentation kann ein Mock wie folgt erstellt werden:

jasmine.createSpyObj(someObject, ['method1', 'method2', ... ]);

Wie stoppt man eine dieser Methoden? Wenn Sie beispielsweise testen möchten, was passiert, wenn eine Methode eine Ausnahme auslöst, wie würden Sie das tun?

Adelin
quelle
3
Sie können versuchen, es mit zu verketten andCallThrough. Es ist nicht klar dokumentiert: /
EricG

Antworten:

117

Sie müssen verketten method1, method2wie EricG kommentierte, aber nicht mit andCallThrough()(oder and.callThrough()in Version 2.0). Es wird an die tatsächliche Umsetzung delegiert .

In diesem Fall müssen Sie and.callFake()die Funktion, die Sie aufrufen möchten , verketten und übergeben (kann eine Ausnahme auslösen oder was auch immer Sie möchten):

var someObject = jasmine.createSpyObj('someObject', [ 'method1', 'method2' ]);
someObject.method1.and.callFake(function() {
    throw 'an-exception';
});

Und dann können Sie überprüfen:

expect(yourFncCallingMethod1).toThrow('an-exception');
zbynour
quelle
7
Jasmin 2.0 geändert hat die Syntax .and.callFake(), .and.callThrough(), .and.returnValue() jasmine.github.io/2.0/introduction.html#section-Spies
alxndr
20

Wenn Sie Typescript verwenden, ist es hilfreich, die Methode als umzuwandeln Jasmine.Spy. In der obigen Antwort (seltsamerweise habe ich keinen Repräsentanten für einen Kommentar):

(someObject.method1 as Jasmine.Spy).and.callFake(function() {
  throw 'an-exception';
});

Ich weiß nicht, ob ich überentwickelt bin, weil mir das Wissen fehlt ...

Für Typescript möchte ich:

  • Intellisense vom zugrunde liegenden Typ
  • Die Fähigkeit, nur die in einer Funktion verwendeten Methoden zu verspotten

Ich fand das nützlich:

namespace Services {
    class LogService {
        info(message: string, ...optionalParams: any[]) {
            if (optionalParams && optionalParams.length > 0) {
                console.log(message, optionalParams);
                return;
            }

            console.log(message);
        }
    }
}

class ExampleSystemUnderTest {
    constructor(private log: Services.LogService) {
    }

    doIt() {
        this.log.info('done');
    }
}

// I export this in a common test file 
// with other utils that all tests import
const asSpy = f => <jasmine.Spy>f;

describe('SomeTest', () => {
    let log: Services.LogService;
    let sut: ExampleSystemUnderTest;

    // ARRANGE
    beforeEach(() => {
        log = jasmine.createSpyObj('log', ['info', 'error']);
        sut = new ExampleSystemUnderTest(log);
    });

    it('should do', () => {
        // ACT
        sut.doIt();

        // ASSERT
        expect(asSpy(log.error)).not.toHaveBeenCalled();
        expect(asSpy(log.info)).toHaveBeenCalledTimes(1);
        expect(asSpy(log.info).calls.allArgs()).toEqual([
            ['done']
        ]);
    });
});
Eric Swanson
quelle
Die akzeptierte Antwort wird für mich nicht kompiliert (Jasmin 2.5), aber diese Lösung hat funktioniert!
Peter Morris
Mini-Verbesserung - todoService: {[key: string]: jasmine.Spy} = jasmine.createSpyObj(...); todoService.anyMethod.and....- Sie müssen nicht jedes Mal auf Spy wirken.
Kai
Danke @Kai, ich habe einige Details hinzugefügt. Ich brauchte (wollte?) Typerkennung als primären Typ und nicht als dynamisches Spionageobjekt. Ich persönlich wollte, dass sich das Objekt wie das reale Objekt verhält und fühlt und dann erst beim Testen zu Spy geworfen wird.
Eric Swanson
3

Winkel 9

Die Verwendung jasmine.createSpyObjist ideal, wenn Sie eine Komponente testen, bei der ein einfacher Service injiziert wird. Zum Beispiel: Nehmen wir an, ich habe in meiner HomeComponent einen HomeService (injiziert). Die einzige Methode im HomeService ist getAddress (). Beim Erstellen der HomeComponent-Testsuite kann ich die Komponente und den Dienst wie folgt initialisieren:

describe('Home Component', () => {
    let component: HomeComponent;
    let fixture: ComponentFixture<HomeComponent>;
    let element: DebugElement;
    let homeServiceSpy: any;
    let homeService: any;

    beforeEach(async(() => {
        homeServiceSpy = jasmine.createSpyObj('HomeService', ['getAddress']);

        TestBed.configureTestingModule({
           declarations: [HomeComponent],
           providers: [{ provide: HomeService, useValue: homeServiceSpy }]
        })
        .compileComponents()
        .then(() => {
            fixture = TestBed.createComponent(HomeComponent);
            component = fixture.componentInstance;
            element = fixture.debugElement;
            homeService = TestBed.get(HomeService);
            fixture.detectChanges();
        });
    }));

    it('should be created', () => {
        expect(component).toBeTruthy();
    });

    it("should display home address", () => { 
        homeService.getAddress.and.returnValue(of('1221 Hub Street'));
        fixture.detectChanges();

        const address = element.queryAll(By.css(".address"));

        expect(address[0].nativeNode.innerText).toEqual('1221 Hub Street');
    });
 });

Dies ist eine einfache Möglichkeit, Ihre Komponente mit zu testen jasmine.createSpyObj. Wenn Ihr Dienst jedoch über komplexere Methoden verfügt, würde ich empfehlen, anstelle von createSpyObj einen mockService zu erstellen. Zum Beispiel: providers: [{ provide: HomeService, useValue: MockHomeService }]

Hoffe das hilft!

Diego Herrera
quelle
0

Aufbauend auf der Antwort von @Eric Swanson habe ich eine besser lesbare und dokumentierte Funktion für meine Tests erstellt. Ich habe auch einige Typensicherheit hinzugefügt, indem ich den Parameter als Funktion eingegeben habe.

Ich würde empfehlen, diesen Code irgendwo in einer gemeinsamen Testklasse zu platzieren, damit Sie ihn in jede Testdatei importieren können, die ihn benötigt.

/**
 * Transforms the given method into a jasmine spy so that jasmine functions
 * can be called on this method without Typescript throwing an error
 *
 * @example
 * `asSpy(translator.getDefaultLang).and.returnValue(null);`
 * is equal to
 * `(translator.getDefaultLang as jasmine.Spy).and.returnValue(null);`
 *
 * This function will be mostly used in combination with `jasmine.createSpyObj`, when you want
 * to add custom behavior to a by jasmine created method
 * @example
 * `const translator: TranslateService = jasmine.createSpyObj('TranslateService', ['getDefaultLang'])
 * asSpy(translator.getDefaultLang).and.returnValue(null);`
 *
 * @param {() => any} method - The method that should be types as a jasmine Spy
 * @returns {jasmine.Spy} - The newly typed method
 */
export function asSpy(method: () => any): jasmine.Spy {
  return method as jasmine.Spy;
}

Die Verwendung wäre wie folgt:

import {asSpy} from "location/to/the/method";

const translator: TranslateService = jasmine.createSpyObj('TranslateService', ['getDefaultLang']);
asSpy(translator.getDefaultLang).and.returnValue(null);
Mr. Wiseguy
quelle