Jest 快照测试因反应导航生成的密钥而失败

Jest snapshot test failing due to react navigation generated key

我的 Jest 测试失败,因为每次测试时密钥中的时间戳都不同 运行:

 FAIL  ./App.test.js
  ✕ renders without crashing (72ms)

  ● renders without crashing

expect(value).toMatchSnapshot()

Received value does not match stored snapshot 1.

- Snapshot
+ Received

@@ -347,11 +347,11 @@
              "index": 0,
              "isTransitioning": false,
              "key": "StackRouterRoot",
              "routes": Array [
                Object {
-                 "key": "id-1519567169760-0",
+                 "key": "id-1519567814666-0",
                  "routeName": "Menu",
                },
              ],
            },
          }

这是我的 App.js 文件:

import React from 'react';
import { StackNavigator } from 'react-navigation';
import Menu from './components/Menu';
import List from './components/List';

const RootStack = StackNavigator(
  {
    Menu: {
      screen: Menu,
    },
    List: {
      screen: List,
    },
  },
  {
    initialRouteName: 'Menu',
  }
);

export default class App extends React.Component {
   render() {
     return <RootStack />;
   }
}

这是我的测试文件:

import React from 'react';
import App from './App';

import renderer from 'react-test-renderer';

test('renders without crashing', () => {
  const rendered = renderer.create(<App />).toJSON();
  expect(rendered).toBeTruthy();
  expect(rendered).toMatchSnapshot();
});

测试 运行s 时是否可以覆盖键值或是否有忽略键的方法?

您可以模拟 non-deterministic 的数据。 https://facebook.github.io/jest/docs/en/snapshot-testing.html#tests-should-be-deterministic

For example, if you have a Clock component that uses Date.now(), the snapshot generated from this component will be different every time the test case is run. In this case we can mock the Date.now() method to return a consistent value every time the test is run:

Date.now = jest.fn(() => 1482363367071);

Now, every time the snapshot test case runs, Date.now() will return 1482363367071 consistently. This will result in the same snapshot being generated for this component regardless of when the test is run.

您可以在 jest 安装文件中使用模拟函数来覆盖键值: https://facebook.github.io/jest/docs/en/configuration.html#setupfiles-array

通过模拟 Date.now 到 Date.now = jest.fn(() => 123) 将键设置为始终相同,因为在引擎盖下反应导航使用它来生成它们的路由键:https://github.com/react-navigation/react-navigation/blob/master/src/routers/KeyGenerator.js

较新版本的 jest 还支持 property matchers,它允许使用匹配器评估快照的某些部分,而不是要求整个快照相同。

下面的示例展示了使用此功能解决 OP 问题的几种不同方法:

it('Should ignore route keys in snapshot', () => {
  const testObject = {
    index: 0,
    isTransitioning: false,
    key: 'StackRouterRoot',
    routes: [
      {
        key: `id-${Date.now()}-0`,
        routeName: 'Menu',
      },
      {
        key: `id-${Date.now()}-1`,
        routeName: 'Splash',
      },
    ],
  };

  expect(testObject).toMatchSnapshot({
    routes: [
      {
        key: expect.stringMatching(/id-\d+?-\d/),
        routeName: 'Menu',
      },
      {
        key: expect.stringMatching(/id-\d+?-\d/),
        routeName: 'Splash',
      },
    ],
  }, 'Explicitly list all routes');

  expect(testObject).toMatchSnapshot({
    routes: expect.arrayContaining([
      expect.objectContaining({
        key: expect.stringMatching(/id-\d+?-\d/),
      }),
    ]),
  }, 'Match any route');
});

生成以下快照:

exports[`<App /> Should ignore route keys in snapshot: Explicitly list all routes 1`] = `
Object {
  "index": 0,
  "isTransitioning": false,
  "key": "StackRouterRoot",
  "routes": Array [
    Object {
      "key": StringMatching /id-\\d\+\?-\\d/,
      "routeName": "Menu",
    },
    Object {
      "key": StringMatching /id-\\d\+\?-\\d/,
      "routeName": "Splash",
    },
  ],
}
`;

exports[`<App /> Should ignore route keys in snapshot: Match any route 1`] = `
Object {
  "index": 0,
  "isTransitioning": false,
  "key": "StackRouterRoot",
  "routes": ArrayContaining [
    ObjectContaining {
      "key": StringMatching /id-\\d\+\?-\\d/,
    },
  ],
}
`;

我最近遇到的另一个可能对这里有帮助的有趣功能是 custom snapshot serializers。使用此功能,我认为您可以为路由对象添加一个序列化程序,该序列化程序将在序列化之前删除密钥或将其替换为静态值。我现在无法提供示例,因为我还没有使用过此功能,但似乎值得一提。

我找到了一个完全不同的解决方案,我已将其作为我自己的类似问题的答案。

我解决了我的问题,但不是通过在许多其他情况下建议的模拟 Date.now。

相反,我改编了一个名为 joeybaker.

的用户在 https://github.com/react-navigation/react-navigation/issues/2269#issuecomment-369318490 上找到的答案

给出的理由如下:

The keys aren't really important for your testing purposes. What you really care about is the routes and the index.

他的代码如下,假设在Redux中使用了actions和reducer:

// keys are date and order-of-test based, so just removed them
const filterKeys = (state) => {
  if (state.routes) {
    return {
      ...state,
      routes: state.routes.map((route) => {
        const { key, ...others } = route
        return filterKeys(others)
      }),
    }
  }
  return state
}

it('clears all other routes', () => {
    const inputState = {}
    const action = { type: AUTH_LOGOUT_SUCCESS }
    const state = filterKeys(reducer(inputState, action))
    expect(state.routes).toBe........
})

我已经针对我的情况进行了调整(我还没有使用 Redux)如下:

test("renders correctly", () => {

  const tree = renderer.create(<StackNavigator />);
  const instance = tree.getInstance();
  const state = filterKeys(instance.state.nav);

  expect(state).toMatchSnapshot();
});

// keys are date and order-of-test based, so just removed them
const filterKeys = (state) => {
  if (state.routes) {
    return {
      ...state,
      routes: state.routes.map((route) => {
        const { key, ...others } = route
        return filterKeys(others);
      }),
    }
  }
  return state;
};

呈现的测试如下所示:

// Jest Snapshot v1

exports[`renders correctly 1`] = `
Object {
  "index": 0,
  "isTransitioning": false,
  "key": "StackRouterRoot",
  "routes": Array [
    Object {
      "index": 0,
      "isTransitioning": false,
      "routeName": "FluidTransitionNavigator",
      "routes": Array [
        Object {
          "routeName": "SignInOrRegister",
        },
      ],
    },
  ],
}
`;