Jest: オブジェクトの等価性チェックで「受信:同じ文字列にシリアル化」

作成日 2019年05月20日  ·  34コメント  ·  ソース: facebook/jest

🐛バグレポート

.toMatchObject()すると、失敗したテストがメッセージReceived: serializes to the same string返されます。

image

再現するには

受け取ったusersオブジェクトをexpectedUsersと照合しようとしています。 MongoDBから返される受信オブジェクトには、チェックしたくないフィールド"__v""_id"含まれています(これらはテストごとに常に変更されます)。 そのため、私は.toMatchObject()を使用しており、 .toEqual()ようなものは使用できません。 私のテストスニペットは以下のとおりです。

test("should show all existing users", async () => {
  const expectedUsers = [
    {
      email: "[email protected]",
      friends: [],
      followers: [],
      following: [],
      blocked: []
    },
    {
      email: "[email protected]",
      friends: ["[email protected]", "[email protected]"],
      followers: [],
      following: [],
      blocked: []
    },
    {
      email: "[email protected]",
      friends: [],
      followers: [],
      following: [],
      blocked: ["[email protected]"]
    }
  ];
  await request(app)
    .get(route(path))
    .expect("Content-Type", /json/)
    .expect(200);

  const users = await User.find();

  expect(users).toMatchObject(expectedUsers);
});

requestsupertest

予想される行動

ここに記載さ

.toMatchObjectを使用して、JavaScriptオブジェクトがオブジェクトのプロパティのサブセットと一致することを確認します。 受信したオブジェクトを、予期されたオブジェクトにないプロパティと照合します。

expectedオブジェクトはreceivedオブジェクトのサブセットであるため、テストに合格することを期待しています。

npx envinfo --preset jest結果

System:
  OS: macOS 10.14.4
  CPU: (12) x64 Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Binaries:
  Node: 10.15.2 - ~/.asdf/shims/node
  npm: 6.9.0 - ~/.asdf/shims/npm
npmPackages:
  jest: ^24.8.0 => 24.8.0 
Bug Report Needs Repro Needs Triage

最も参考になるコメント

私もこの問題を経験しています。 これが私の回避策です:

expect(JSON.stringify(result.current)).toEqual(JSON.stringify(expected));

全てのコメント34件

cc @pedrottimark

@sabrieleはい、 toMatchObjectを選択するのは理にかなっています。 したがって、トラブルシューティングを行うことができます。

  • console.log(users)からの出力を貼り付けていただけますか
  • アプリケーションはmongoose使用しますか

@sabriele Jestコードを読んでMongoDBについて推測すると、 users配列には、 toMatchObjectが無視する必要がある(ただし無視しない)非インデックスプロパティが含まれている可能性があります。

それらを取り除くための回避策は次のとおりです。

expect([...users]).toMatchObject(expectedUsers)

回避users前に受け取った

同じ問題があります。 これがreactカスタムフックのテストです:

import {renderHook} from 'react-hooks-testing-library'

import useTheme, {DEFAULT_THEME} from 'components/globalStyle/useTheme'

it('should set the global theme', () => {
  const setTheme = () => {}
  const expected = {...DEFAULT_THEME, setTheme}
  const {result} = renderHook(useTheme)

  expect(result.current).toMatchObject(expected)
})

このテストは次のエラーを返します。

Error: expect(received).toMatchObject(expected)

Expected: {"palette": "dark", "setTheme": [Function setTheme], "textSize": "normal"}
Received: serializes to the same string

@pedrottimarkが提案した浅いコピーのトリックを

import {renderHook} from 'react-hooks-testing-library'

import useTheme, {DEFAULT_THEME} from 'components/globalStyle/useTheme'

it('should set the global theme', () => {
  const Mock = jest.fn()
  const setTheme = new Mock()
  const {result} = renderHook(useTheme)
  const expected = {...DEFAULT_THEME, setTheme}

  expect(result.current).toMatchObject(expected)
})

const setTheme = jest.fn()設定が機能しませんでした🤷‍♂️

Error: expect(received).toMatchObject(expected)

- Expected
+ Received

  Object {
    "palette": "dark",
-   "setTheme": [Function mockConstructor],
+   "setTheme": [Function setTheme],
    "textSize": "normal",
  }

@matchatype https://github.com/facebook/jest/issues/8475#issuecomment -495943549の問題が#8166のようで、深等式マッチャーが参照IDに従って関数を比較する場合、期待値として非対称マッチャーをお勧めします、 https://jestjs.io/docs/en/expect#expectanyconstructorを参照して

it('should set the global theme', () => {
  const setTheme = expect.any(Function)
  const expected = {...DEFAULT_THEME, setTheme}
  const {result} = renderHook(useTheme)

  expect(result.current).toMatchObject(expected)
})

それは確かに機能します! ただし、まだ混乱しています。すべての例で同じ動作が発生するはずです。 代わりに、それぞれが完全に異なる応答をトリガーします。

  • 受信:同じ文字列にシリアル化します。
  • テスト合格;
  • エラー:expect(received).toMatchObject(expected)。

serializes to the same stringを表示する最近の変更により、マッチャーの比較とレポートのフィードバックの間に矛盾がある場合がより明確になります。

@matchatypeあなたが説明する場合:

  • 比較は、(予期しないが)正しいこと() => {}またはjest.fn()期待値として参照上(ASではなく、同じインスタンス)フックによって返された関数に等しくありません
  • 等しくない値は同じシリアル化を持つ可能性があるため、レポートは混乱します

深い等式マッチャーは、関数のさまざまなインスタンスを比較し

  • シンボルのようにSymbol()Symbol()と等しくありません
  • 配列やオブジェクトとは異なり、 [0][0]等しいか、 {key: 'value'}{key: 'value'}等しい

返されるデータ構造をツリーと考えると、プリミティブ値をリーフとしてアサートすることと、関数またはシンボルをアサートすること(呼び出し元が引数として提供しない場合)には違いがあります。

あなたが見つけた回避策が問題を解決する理由がわかりません:)

Jestの長期的な目標は、比較とレポートの間のこのようなギャップを埋めることです。

serializes to the same stringは、元のhttps://github.com/facebook/jest/issues/8475#issue-446046819の別の問題の症状です

  1. 比較が正しくありません: toMatchObjectマッチャーは、予想されるサブセットに従ってプロパティを無視するのではなく、 toEqualマッチャーと同じ配列の非インデックスプロパティ(つまり、シンボルまたは非数値文字列)を比較します
  2. getObjectSubsetヘルパーは、予期されたサブセットに含まれていたとしても、レポートの受信値の非インデックスプロパティを無視します
  3. pretty-formatパッケージは、 getObjectSubset含まれていても、インデックス以外のプロパティを無視します

これらの問題を解決することの難しさ:2。中、1。難しい、3。壊れる

非常に役に立ちました@pedrottimarkありがとうございました🙏はい、回避策が実際に通過したという事実は私を完全に困惑させました。

私も同じ問題を抱えています。私にとっては、問題はオブジェクトにある関数に起因します。
私にとっての解決策は、関数をjest.fn()モックし、それを入力小道具と期待されるオブジェクトに配置することです。

jestのtoEqualは2つのオブジェクトを比較できます、それはクールです(jsでは '=='で直接比較することはできません)が、オブジェクトに関数(()=> {}など)が含まれている場合、比較に問題があります。

こんにちは@pedrottimark 、返信が遅れたことをお詫びします。 これは週末のプロジェクトで、私はただ仕事に忙殺されました。

はい、私はマングースを使用しています。 console.log(users)console.log([...users])の結果を比較しましたが、まったく同じです。

[ { friends: [],
    followers: [],
    following: [],
    blocked: [],
    _id: 5cfbb57e37912c8ff6d2f8b1,
    email: '[email protected]',
    __v: 0 },
  { friends:
      [ '[email protected]', '[email protected]' ],
    followers: [],
    following: [],
    blocked: [],
    _id: 5cfbb57e37912c8ff6d2f8b2,
    email: '[email protected]',
    __v: 0 },
  { friends: [],
    followers: [],
    following: [],
    blocked: [ '[email protected]' ],
    _id: 5cfbb57e37912c8ff6d2f8b3,
    email: '[email protected]',
    __v: 0 } ]

@matchatypeと同じように、私も浅いコピーのトリックを試しましたが、同じエラーが発生しました。

これをトラブルシューティングするのを手伝ってくれてありがとう! ほんとうにありがとう。

@sabriele出力ありがとうございます。 ローカルテストファイルにコピーして貼り付けると、 '5cfbb57e37912c8ff6d2f8b1'ではなく5cfbb57e37912c8ff6d2f8b1ような_idプロパティの値の構文エラーが発生します

これは、マングースがuserオブジェクトインスタンスにいくつかのメソッドを提供していることを確認しています。

編集:つまり、 console.logからのデフォルト出力を何らかの形で「改善」したメソッドです。

アレイの浅いコピーが役に立たなかった場合、次のステップは次のようになります。

expect(users.map(user => user.toObject())).toMatchObject(expectedUsers);

https://mongoosejs.com/docs/api.html#document_Document-toObjectを参照して

このドキュメントをプレーンなjavascriptオブジェクトに変換し、MongoDBに保存できるようにします。

それが解決策である場合、私は問題が何であるかを理解するためにいくつかのフォローアップの質問があります。

toObjectは私のために働きます

@patranテストでmongooseを使用してMongoDBからオブジェクトの配列を取得した場合、 toMatchObjectの問題を理解できます。元の配列と最初のオブジェクトに、 console.log(…)を追加できますか?

  • Object.getOwnPropertyDescriptors(array)配列インデックスプロパティをコピーしてから削除する
  • Object.getOwnPropertyDescriptors(array[0])データのプロパティをコピーしてから削除します

編集後に結果を貼り付けて、マングースによって追加されていないプロパティを削除します。 ありがとうございました!

同じ問題があります

バッファの比較でも同様の問題があります。
expect(a).toEqual(b)は「同じ文字列にシリアル化」をスローします
expect(a.equals(b)).toBe(true)は正常に機能します

Object.getOwnPropertyDescriptorsを使用してこれらのオブジェクト間の違いを見つけようとしましたが、同じように見えます。

toMatchObjectを使用すると、「同じ文字列にシリアル化する」という問題が発生します。 オブジェクトには関数が定義されており、toMatchObjectが失敗した理由でした。 私はそれらをモックすることで問題を回避しました:

const mockFunctions = <T extends Record<string, any>>(obj: T, mock: any): T => {
  const copy = { ...obj };
  Reflect.ownKeys(copy)
    .filter(key => typeof Reflect.get(copy, key) === "function")
    .forEach(key => Reflect.set(copy, key, mock));
  return copy;
};

toMatchObjectが期待どおりに機能するためには、両方のオブジェクトで同じジェストモックを使用することが重要でした。

const objectToCompare = (name: string) => {
  const nameAsFunc = (): string => name;

  return {
    name,
    nameAsFunc
  };
};

describe("toMatchObject tests", () => {
  it("can compare objects with functions", () => {
    const mock = jest.fn();

    const first = objectToCompare("name");
    const second = objectToCompare("name");

    // Gives "serializes to the same string"
    expect(first).toMatchObject(second);

    // Works
    expect(mockFunctions(first, mock)).toMatchObject(mockFunctions(second, mock));
  });
});

@ matchatype #8475(コメント)の問題が。https: //jestjs.io/docs/en/を参照して

it('should set the global theme', () => {
  const setTheme = expect.any(Function)
  const expected = {...DEFAULT_THEME, setTheme}
  const {result} = renderHook(useTheme)

  expect(result.current).toMatchObject(expected)
})

それは私にとってもうまくいきました。 ありがとう!

私もこの問題を経験しています。 これが私の回避策です:

expect(JSON.stringify(result.current)).toEqual(JSON.stringify(expected));

@manhhailuaどうもありがとうございました! これは何時間もの苦痛の後で私のために働いた。

私もこの問題を経験しています。 これが私の回避策です:

expect(JSON.stringify(result.current)).toEqual(JSON.stringify(expected));

@pedrottimark皆さんはこれをすぐに修正する予定ですか? また、浅いレンダリングを使用していて、悪いテスト結果が発生します。 😕

これが私のテストコードです:

expect(shallowResult.props.children).toEqual(
            [<Todo todo={fakeTodosData.data[0]} />,
            <Todo todo={fakeTodosData.data[1]} />]
        );

shallowResult.props.childrenが正しい場合、私のテストでは次のようになります。

  Expected: [<Todo todo={{"description": "", "id": 100, "title": "Text!"}} />, <Todo todo={{"description": "More text...", "id": 42, "title": "Other Text"}} />]
    Received: serializes to the same string

^(恐ろしい出力であり、実際に変更する必要があります)

マッチャーを「toContainEqual」に変更すると、次のように出力されます。

  Expected value: [<Todo todo={{"description": "", "id": 100, "title": "Text!"}} />, <Todo todo={{"description": "More text...", "id": 42, "title": "Other Text"}} />]
    Received array: [<Todo todo={{"description": "", "id": 100, "title": "Text!"}} />, <Todo todo={{"description": "More text...", "id": 42, "title": "Other Text"}} />]

(^結果がまったく同じであることを示す失敗したテスト。これは_非常に紛らわしい_であり、実際に変更する必要があります)

@manhhailuaの使用しても、私には機能しません。 コンポーネントをループでレンダリングするときに必要な「キー」フィールドは、テスト出力に隠されているようです。 これが私の文字通りのテストの失敗です:

    Expected: "[{\"key\":null,\"ref\":null,\"props\":{\"todo\":{\"id\":100,\"title\":\"Text!\",\"description\":\"\"}},\"_owner\":null,\"_store\":{}},{\"key\":null,\"ref\":null,\"props\":{\"todo\":{\"id\":42,\"title\":\"Other Text\",\"description\":\"More text...\"}},\"_owner\":null,\"_store\":{}}]"
    Received: "[{\"key\":\"key0\",\"ref\":null,\"props\":{\"todo\":{\"id\":100,\"title\":\"Text!\",\"description\":\"\"}},\"_owner\":null,\"_store\":{}},{\"key\":\"key1\",\"ref\":null,\"props\":{\"todo\":{\"id\":42,\"title\":\"Other Text\",\"description\":\"More text...\"}},\"_owner\":null,\"_store\":{}}]"

@pedrottimarkあなたはこの「react-test-renderer /

ありがとう!

結局、私のテストはこれで合格です(「キー」フィールドを忘れていて、文字列化された比較を行うまで、それが欠落していることに気づいていませんでした):

 expect(JSON.stringify(shallowResult.props.children)).toEqual(
            JSON.stringify(
                [<Todo todo={fakeTodosData.data[0]} key={'key0'}/>,
                <Todo todo={fakeTodosData.data[1]} key={'key1'}/>]
            )
        );

fyi、 .toBe.toEqualに交換すると、私の場合に役立ちました:)

循環構造のJSON証明:

// Workaround when Using .toMatchObject() returns failing test with message Received: serializes to the same string
// https://github.com/facebook/jest/issues/8475
const getCircularReplacer = () => {
    const seen = new WeakSet();
    return (key, value) => {
        if (typeof value === 'object' && value !== null) {
            if (seen.has(value)) {
                return;
            }
            seen.add(value);
        }
        return value;
    };
};

const isStringifiedComparisonEqual = (a: Object, b: Object): boolean =>
    JSON.stringify(a, getCircularReplacer()) === JSON.stringify(b, getCircularReplacer());



expect(isStringifiedComparisonEqual(objectA, objectB));

私の場合、私は:

const obj = { value: 'value', array: ['array'] };
expect(obj).toMatchObject({ ... });

エラーが発生しましたが、ネストされた配列をexpect.arrayContaining(['array'])toMatchObject )でラップすることで解決できました。 その場合、Jestはネストされた配列を自動的に解決しない可能性があるためです。

オブジェクトにメソッドが含まれている場合、このスレッドにジャンプすると、次のようになります。

      const a = {
        getSomething: () => ({
          getSomethingElse: () => ({
            something: 'I want',
          }),
        }),
      };

      const b = {
        getSomething: () => ({
          getSomethingElse: () => ({
            something: 'I want',
          }),
        }),
      };

      expect(a).toMatchObject(b);  //    _Expected: {"getSomething": [Function getSomething]} Received: serializes to the same string_

こんにちは。 すでに問題を説明しているメッセージを見逃してしまった場合は申し訳ありませんが、複製付きのサンドボックスを作成しました。

https://codesandbox.io/s/nameless-violet-vk4gn

結果については、 src/index.test.jsソースと[テスト]タブを参照してください

@pedrottimark

[インデックス以外のプロパティ]を削除するための回避策は次のとおりです。

expect([...users]).toMatchObject(expectedUsers)

users.slice(0)は、インデックス以外のプロパティも削除します。 もっと速いのでしょうか?

users.slice(0)は、インデックス以外のプロパティも削除します。 もっと速いのでしょうか?

違いはごくわずかですhttps://jsperf.com/slice-vs-spread-2

「同じ文字列にシリアル化する」を無効にして、確実に解決できるようにする方法はありますか? たぶん、Jestの追加構成ですか? 深くネストされたオブジェクト、オブジェクトメソッドなどを比較する必要があるたびに、回避策を探す必要があるのは非常に面倒です。

thx @manhhailua

@DnEgorWebを使用してこの機能を実現するには、オブジェクトを自分でシリアル化し、結果を比較できます。 私のユースケースでは、オブジェクトが実際にずっと同じであることを確認する必要があるため、この動作は良いことです

私もこの問題を抱えていましたが、 expectexpect中にラップして、スローエラーをキャッチできることがわかりました。

    expect(() => {
        expect(toReact(children)).toEqual([
            "Hello, ",
            <A href="https://google.com">
                world
            </A>,
            "!",
        ])
    }).toThrow("serializes to the same string")

これが誰かに役立つことを願っています。 ちなみに、正規表現を使用してスローメッセージを実際にテストできます: https

多分これは他の誰かを助けるでしょう。 テストの作成中に循環依存関係を導入した後、このエラーが発生しました。 循環依存関係を削除すると、問題が解決しました。

このページは役に立ちましたか?
0 / 5 - 0 評価