javascript - Jest spyOn face ca funcția să fie 'undefined'


Construiesc o aplicație React Native cu TypeScript și scriu testele mele unitare folosind Jest.

În prezent scriu testele unitare pentru ecranul de încărcare:

export class LoadingScreen extends Component<Props, object> {
  componentDidMount = () => {
    const { dispatch, navigation } = this.props;
    getToken()
      .then(key => {
        if (typeof key === "string") {
          getBootData(dispatch)
            .then(() => navigation.navigate("HomeScreen"))
            .catch(err => {
              alert(err);
              // TODO: Hide the splash screen, after componentDidMount finished.
              navigation.navigate("LoginScreen");
            });
        } else {
          // TODO: Hide the splash screen, after componentDidMount finished.
          navigation.navigate("LoginScreen");
        }
      })
      .catch(() => navigation.navigate("LoginScreen"));
  };
  // ... render() n' stuff.

Așa cum poți vedea, după ce componenta se montează, verific dacă există un token. Și în funcție de token, sincronizez aplicația cu serverul (în getBootData) și navighez către ecranul respectiv - fie Home, fie înapoi la Login.

Iată testul pe care l-am scris pentru această metodă a ciclului de viață:

describe("life cycle methods", () => {
  describe("componentDidMount", () => {
    const props = createTestProps({});
    const wrapper = shallow<LoadingScreen>(<LoadingScreen {...props} />);
    const getTokenSpy = jest.spyOn(secureStoreFunctions, "getToken");
    const getBootDataSpy = jest.spyOn(bootDataFunctions, "getBootData");

    beforeEach(() => {
      jest.resetAllMocks();
      getBootDataSpy.mockResolvedValueOnce(true);
      wrapper.instance().componentDidMount();
    });

    it("should call getToken", () => {
      expect(getTokenSpy).toHaveBeenCalledTimes(1);
    });

    it("should call getBootData", () => {
      expect(getBootDataSpy).toBeCalledTimes(1);
    });
  });
});

Scopul meu era să spionez metoda getToken() pentru a vedea dacă a fost apelată. Dar problema este că acest lucru duce la eroarea:

● LoadingScreen › life cycle methods › componentDidMount › should call getToken

    TypeError: Cannot read property 'then' of undefined

      17 |   componentDidMount = () => {
      18 |     const { dispatch, navigation } = this.props;
    > 19 |     getToken()
         |     ^
      20 |       .then(key => {
      21 |         if (typeof key === "string") {
      22 |           getBootData(dispatch)

      at LoadingScreen.componentDidMount (app/screens/Loading/LoadingScreen.tsx:19:5)
      at Object.beforeEach (app/screens/Loading/LoadingScreen.test.tsx:39:28)

BTW: Testul pentru getBootData eșuează și el. Credeam că spyOn ar trebui să păstreze implementarea originală a funcției intactă? 🤔 Dar aparent o înlocuiește cu undefined :(

Informații suplimentare

Așa arată getToken:

import { getGenericPassword } from "react-native-keychain";
export const getToken = (): Promise<boolean | string> =>
  getGenericPassword().then(
    creds => (typeof creds === "boolean" ? creds : creds.password)
  );

De asemenea, am un mock simplu pentru getGenericPassword din React Native Keychain într-un folder __mocks__/ adiacent modulelor mele node, astfel încât funcția să fie mock-uită automat de Jest în alte teste care necesită această funcție:

const token = "abcdefghijklmnopqrstuvwxyz0123456789";
const credentials = {
  username: "session",
  password: token
};

export const setGenericPassword = jest.fn(
  (username, password) => new Promise((resolve, reject) => resolve(true)) // eslint-disable-line no-unused-vars
);

export const getGenericPassword = jest.fn(
  () => new Promise((resolve, reject) => resolve(credentials)) // eslint-disable-line no-unused-vars
);

export const resetGenericPassword = jest.fn(() => new Promise((resolve, reject) => resolve(true))); // eslint-disable-line no-unused-vars

S-ar putea ca aceste două simulări să se suprapună într-un fel și să cauzeze nedefinirea? Ceea ce vorbește împotriva acestui lucru este că nu am o simulare globală getBootData, care - după cum s-a menționat mai sus - eșuează în mod similar.

Ce se întâmplă? De ce face spyOn ca funcția să devină undefined?