Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use jest-diff in toHaveBeenCalledWith matcher results #676

Open
olivierlacan opened this issue Feb 7, 2024 · 0 comments
Open

Use jest-diff in toHaveBeenCalledWith matcher results #676

olivierlacan opened this issue Feb 7, 2024 · 0 comments

Comments

@olivierlacan
Copy link

olivierlacan commented Feb 7, 2024

Feature Request

Description

toHaveBeenCalledWith is a very helpful matcher, its output less so.

Jest users shouldn't have to scan JS objects to try and figure out what specific property happens to differ.

Possible solution

Here's a (very) naive implementation of this feature as toHaveBeenCalledWithDiff which wraps toHaveBeenCalledWith and simply runs jest-diff on expected & actual:

// add this to a file listed in `setupFilesAfterEnv` in `jest.config.ts`,
// for example `jest.setup.ts`: 

import { diff } from "jest-diff";

expect.extend({
  toHaveBeenCalledWithDiff(received, ...expected) {
    // Check if the function was called
    if (received.mock.calls.length === 0) {
      return {
        message: () => `expected mock function to be called but it was not`,
        pass: false,
      };
    }

    // Loop through all calls to find a match
    const calls = received.mock.calls;
    for (let i = 0; i < calls.length; i++) {
      const actual = calls[i];
      const pass = this.equals(actual, expected);

      if (pass) {
        return {
          message: () => `all good`,
          pass: true, // The actual and expected arguments match
        };
      } else {
        // Generate and log the diff if the arguments do not match
        const diffString = diff(expected, actual, {
          expand: this.expand,
        });

        return {
          message: () => `expected mock function to be called with:\n${this.utils.printExpected(expected)}\nbut it was called with:\n${this.utils.printReceived(actual)}\n\nDifference:\n\n${diffString}`,
          pass: false, // The actual and expected arguments do not match
        };
      }
    }

    // If none of the calls match the expected arguments
    return {
      message: () => `expected mock function to be called with ${this.utils.printExpected(expected)} but it was not`,
      pass: false,
    };
  }
}); 

Motivation

Given the following expectation:

expect(mockService.createOrUpdate).toHaveBeenCalledWithDiff(
  expect.objectContaining({
    id: expect.any(String),
    authorIds: [],
    description: mock.description,
    longDescription: mock.longDescription,
    educationLevel: EducationLevelType.BEGINNER,
    imageUrl: expect.any(String),
    opportunityId: mock.opportunityId,
    firstPublishedAt: expect.any(Date),
    retiredAt: undefined,
    type: "video",
    title: mock.title,
  }),
);

Is this really useful output? Or could Jest as a testing tool actually help programmers focus on the differences that matter?

expected mock function to be called with:
[ObjectContaining {"authorIds": [], "description": "Deficio aetas eum capitulus a.", "educationLevel": "BEGINNER", "firstPublishedAt": Any<Date>, "id": Any<String>, "imageUrl": Any<String>, "longDescription": "Tersus adicio varius quo bardus ulterius speculum deprimo temptatio. Angulus surgo tondeo. Tandem vita decretum adulescens corpus advenio deleo adhuc cruciamentum.", "retiredAt": undefined, "title": "curo totam cupressus", "type": "video"}]
but it was called with:
[{"authorIds": [], "description": "Deficio aetas eum capitulus a.", "educationLevel": "BEGINNER", "firstPublishedAt": 2024-02-06T03:20:04.037Z, "id": undefined, "imageUrl": "https://picsum.photos/seed/DFLZ75/640/480", "longDescription": "Tersus adicio varius quo bardus ulterius speculum deprimo temptatio. Angulus surgo tondeo. Tandem vita decretum adulescens corpus advenio deleo adhuc cruciamentum.", "retiredAt": undefined, "title": "curo totam cupressus", "type": "video"}]

Can you. tell me what's causing the test to fail here? Should I expect you to run a manual diff on this output when jest-diff is already used within Jest itself to provide similar diffs?

Outcome

Wouldn't toHaveBeenCalledWith be a much more valuable tool if it provided this output instead?

- Expected
+ Received

@@ -1,15 +1,15 @@
  Array [
-   ObjectContaining {
+   Object {
      "authorIds": Array [],
      "description": "Deficio aetas eum capitulus a.",
      "educationLevel": "BEGINNER",
-     "firstPublishedAt": Any<Date>,
-     "id": Any<String>,
-     "imageUrl": Any<String>,
+     "firstPublishedAt": 2024-02-06T03:20:04.037Z,
+     "id": undefined,
+     "imageUrl": "https://picsum.photos/seed/DFLZ75/640/480",
      "longDescription": "Tersus adicio varius quo bardus ulterius speculum deprimo temptatio. Angulus surgo tondeo. Tandem vita decretum adulescens corpus advenio deleo adhuc cruciamentum.",
      "opportunityId": "bef7fbc0-e964-4770-95b8-9a6667effac4",
      "retiredAt": undefined,
      "title": "curo totam cupressus",

Granted, it does highlight type expectations as differences which can be a bit confusing, but it does help narrow the search far more quickly: id was expected to be Any<String> but it's undefined.

All done, ready to fix the test now. 😄

PS: I checked toContain* matchers in jest-extended and was surprised they don't seem to offer this either. But it's possible I've missed something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant