typescript - Spionera på en metod som skickas in i en mockad node-modul


Jag bygger en klient som kommer att omsluta SignalR-biblioteket:

export class RealTimeCommunicator {

  /** SignalR hub connection */
  private hubConnection: HubConnection | null = null;

  private buildConnection(url: string): HubConnection {
    return new HubConnectionBuilder()
    .withUrl(url)
    .build();
  }

  public subscribe(onStart: () => void, success: (data: IVehicleInfo) => void, fail?: (err: Error) => void): () => void {
    this.hubConnection = this.buildConnection("/someurl");
    this.hubConnection.on("send", data => {
      success(data);
    });

    this.hubConnection.start()
      .then(onStart)
      .catch(err => fail && fail(err));

    return this.unsubscribe;
  }

  public subscribeToVehicleDetails(vehicleId: number, success: (data: IVehicleInfo) => void, fail?: (err: Error) => void): () => void {
    return this.subscribe(() => this.hubConnection!.invoke("EnrolInVehicleGroupAsync", vehicleId), success, fail);
  }

Jag vill testa det, men mocka ut SignalR, så jag lade till __mocks__/@aspnet/signalr.ts i lösningen:

let SignalR = jest.genMockFromModule("@aspnet/signalr");

class HubConnection {

  public on(methodName: string, callback: () => string): Promise<void> {
    this.successCallback = callback;
    return Promise.resolve()
  }

  public start(): Promise<void> { return Promise.resolve() }

  public successCallback: () => string = () => "failure";
}

let hubConnection = new HubConnection();

let HubConnectionBuilder = () => ({
  withUrl: (url) => ({
    build: () => hubConnection
  })
});

SignalR = {
  HubConnectionBuilder: HubConnectionBuilder,
  HubConnection: hubConnection
};

module.exports = SignalR;

Jag försöker spionera på on-metoden för att säkerställa att den anropades, och som ett extra mål bekräfta att min framgångshanterare registrerades korrekt.

import { RealTimeCommunicator } from "./RealTimeCommunicator";
jest.mock('SignalR');

describe("RTC", () => {
  test("foo", () => {
    const ConnectionBuilder = require("SignalR").HubConnectionBuilder;

    const connection = new ConnectionBuilder().withUrl("").build();

    const spy = jest.spyOn(connection, 'on');
    expect(spy).toHaveBeenCalled();  //Expectation fails
    const successText = "success!";
    const successCallBack: () => string = () => successText;
    const rtc = new RealTimeCommunicator();

    rtc.subscribeToVehicleDetails(1, successCallBack, _ => _);
    expect(connection.successCallback()).toBe(successText); // connection.successCallback is undefined
  })
});

Om jag placerar brytpunkter i min SignalR-mock kan jag se att on anropas, men spionen ser det inte. Även successCallback som jag fäste på min mock kommer tillbaka som undefined

Svar #1

Kan du tillhandahålla något fungerande exempel (t.ex. i form av ett repo) för att återskapa ditt problem? Såvitt jag kan se finns det några brister i de tillhandahållna kodsnuttarna.

Först och främst, varför fungerar jest.mock('SignalR'); faktiskt? SignalR är inte ett modul som borde ge ett fel. Du borde mocka den modul du faktiskt importerar: jest.mock('@aspnet/signalr');. Förutom det, består den första delen av testet av att testa ett tredjepartsbibliotek - varför gör du detta överhuvudtaget, förutom att kontrollera om din mock fungerar?

För det andra kontrollerar du om on-metoden kallades omedelbart efter att spionen skapades. Spionen registrerar dock endast anrop som görs efter att den skapats. Vad du kanske vill göra är att göra on-metoden till en spion som standard:

// in __mocks__/@aspnet/signalr.ts

class HubConnection {

  public on = jest.fn().mockImplementation((methodName: string, callback: () => string) => {
    this.successCallback = callback;
    return Promise.resolve()
  });


  // ...

}

För det tredje är anslutningen som du skapar i testet en annan än den som du skapar senare med subscribeToVehicleDetails, därför skulle successCallback ändå returnera "failure" om den skulle fungera.

Svar #2

Du är nära, du behöver bara två ändringar.

Returnera resultatet av success(data):

public subscribe(onStart: () => void, success: (data: IVehicleInfo) => void, fail?: (err: Error) => void): () => void {
  this.hubConnection = this.buildConnection("/someurl");
  this.hubConnection.on("send", data => {
    return success(data);   // return the result
  });

  this.hubConnection.start()
    .then(onStart)
    .catch(err => fail && fail(err));

  return this.unsubscribe;
}

Vänta med att hävda att spy har blivit anropad tills efter att du anropar rtc.subscribeToVehicleDetails:

const spy = jest.spyOn(connection, 'on');
const successText = "success!";
const successCallBack: () => string = () => successText;
const rtc = new RealTimeCommunicator();

rtc.subscribeToVehicleDetails(1, successCallBack, _ => _);
expect(spy).toHaveBeenCalled();   // SUCCESS
expect(connection.successCallback()).toBe(successText);   // SUCCESS