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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to overload matchers with expect.extend and call the previous implementation if any. #6243

Closed
gnapse opened this issue May 24, 2018 · 6 comments

Comments

@gnapse
Copy link

gnapse commented May 24, 2018

馃殌 Feature Proposal

When extending jest with new custom matchers, I did not find anything about what happens if a new custom matcher is named exactly the same as an existing matcher. The code suggests it simply overrides the previous matcher.

My suggestion is that we keep the previous implementation, and provide the ability for the new implementation to call the previous one, if any.

Motivation

This would allow to really extend existing matchers, using the same name for new purposes.

For instance, imagine a custom matcher named toBeEmpty that checks if the value is an array or object that is empty, as provided here. Then imagine jest-dom wants to add a toBeEmpty to check if a DOM node is empty. But given it has such a potentially common name, it wouldn't want to break other uses of that matcher. If the argument it receives is a DOM node, it does its thing, otherwise it calls the previous implementation, if it exists, or throws an error about the argument type not supported.

Example

If implemented, I imagined that the previous implementation could be provided as a new property of this, perhaps this.super:

function toBeEmpty(target) {
  if (!(htmlElement instanceof HTMLElement)) {
    if (this.super) {
      return this.super(target);
    }
    throw new Error('expected a HTML element');
  }

  // Continue with checking if the html element `target` is empty or not...
}

this.super should be undefined if there was no previous implementation of a matcher with the same name.

Inspiration

I originally got the idea from chai-dom's to.be.empty matcher, that does exactly what is described above, to avoid breaking the standard chai to.be.empty matcher.

Downside

Maybe having this capability could be considered bad practice, that the same name can mean different things. I can imagine that it could be a nightmare to support in a typed environment, such as flow or TypeScript.

Pitch

I think this could be implemented in userland:

function extendJestExpect(matchers) {
  Object.entries(matchers).forEach(([name, matcher]) => {
    if (expect[name] != null) {
      matcher.super = expect[name];
    }
  });
  expect.extend(matchers);
}

However repositories of custom matchers that wanted to support this, would need to instruct their users to use something like this. And each one of them would need to provide a utility like the above one. Providing this by the library's core means to add custom matchers would make it official, not to mention that it would be better implemented, and less opportunity to break with new versions than user implementations.

I'm willing to work on this myself if accepted.

@gaearon
Copy link
Contributor

gaearon commented Aug 10, 2018

I also found myself wanting this. I want to keep using toThrow() matcher in the React test suite, but augment it with some special behavior that's specific to our test setup. I'm not sure how to do this.

@gaearon
Copy link
Contributor

gaearon commented Aug 10, 2018

For now I'm hacking around it like this.

  function wrapThrowingMatcher(originalMatcher) {
    return function (...args) {
      // ...
      // do some custom stuff
      // ...
      return originalMatcher(...args);
    };
  }

  const builtInThrowingMatchers = require('expect/build/to_throw_matchers').default;
  expect.extend({
    toThrow: wrapThrowingMatcher(builtInThrowingMatchers.toThrow),
    toThrowError: wrapThrowingMatcher(builtInThrowingMatchers.toThrowError),
  });

Not very happy but I need it..

@darekkay
Copy link

The proposed solution works if the different implementation are exclusive, like the toBeEmpty example, where jest-dom addresses only HTMLElements and jest-extended addresses strings and arrays. What if a third library introduces another toBeEmpty and handles both cases within? I'm not sure if jest can provide a generic solution to handle such cases. Providing a warning (#9678) and leaving up the setup to the user might be the best idea.

@adrianmcli
Copy link

This would be really helpful.

@github-actions
Copy link

This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actions github-actions bot added the Stale label Feb 25, 2022
@github-actions
Copy link

This issue was closed because it has been stalled for 7 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.

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

No branches or pull requests

5 participants