question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

🚀 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.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:11
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
darekkaycommented, May 18, 2020

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.

2reactions
gaearoncommented, 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…

Read more comments on GitHub >

github_iconTop Results From Across the Web

Testing TypeScript with Jest: "no overload matches this call"
My class, CDCDataSource extends the abstract class RESTDataSource which itself extends the abstract class DataSource . RESTDataSource has the ...
Read more >
Expect - Jest
This is a deep-equality function that will return true if two objects have the same values (recursively). this.expand ​. A boolean to let...
Read more >
gMock Cookbook | GoogleTest
If you expect an overloaded function to be called, the compiler may need some help on which overloaded version it is. To disambiguate...
Read more >
More types - mypy 0.991 documentation
An overloaded function must consist of two or more overload variants followed by an implementation. The variants and the implementations must be adjacent...
Read more >
CUDA C++ Programming Guide - NVIDIA Documentation Center
Multi-Device System shows how the programming model extends to a system with ... On devices of compute capability 5.3 (Maxwell) and earlier, the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found