Extending third party module that is globally exposed
A simple way is:
customMatchers.ts
declare global {
namespace jest {
interface Matchers<R> {
// add any of your custom matchers here
toBeDivisibleBy: (argument: number) => {};
}
}
}
// this will extend the expect with a custom matcher
expect.extend({
toBeDivisibleBy(received: number, argument: number) {
const pass = received % argument === 0;
if (pass) {
return {
message: () => `expected ${received} not to be divisible by ${argument}`,
pass: true
};
} else {
return {
message: () => `expected ${received} to be divisible by ${argument}`,
pass: false
};
}
}
});
my.spec.ts
import "path/to/customMatchers";
test('even and odd numbers', () => {
expect(100).toBeDivisibleBy(2);
expect(101).not.toBeDivisibleBy(2);
});
OK, so there are a few issues here
When a source file (.ts
or .tsx
) file and a declaration file (.d.ts
) file are both candidates for module resolution, as is the case here, the compiler will resolve the source file.
You probably have two files because you want to export a value and also modify the type of the global object jest
. However, you do not need two files for this as TypeScript has a specific construct for augmenting the global scope from within a module. That is to say, all you need is the following .ts
file
myMatcher.ts
// use declare global within a module to introduce or augment a global declaration.
declare global {
namespace jest {
interface Matchers {
myMatcher: typeof myMatcher;
}
}
}
export default function myMatcher<T>(this: jest.MatcherUtils, received: T, expected: T) {
const pass = received === expected;
return {
pass,
message: () => `expected ${pass ? '!' : '='}==`
};
}
That said, if you have such a situation, it is a good practice to perform the global mutation and the global type augmentation in the same file. Given that, I would consider rewriting it as follows
myMatcher.ts
// ensure this is parsed as a module.
export {};
declare global {
namespace jest {
interface Matchers {
myMatcher: typeof myMatcher;
}
}
}
function myMatcher<T>(this: jest.MatcherUtils, received: T, expected: T) {
const pass = received === expected;
return {
pass,
message: () => `expected ${pass ? '!' : '='}==`
};
}
expect.extend({
myMatcher
});
someTest.ts
import './myMatcher';
it('should work', () => {
expect('str').myMatcher('str');
});
Answer by @AluanHaddad is almost correct, sans a few types. This one works:
export {};
declare global {
namespace jest {
interface Matchers<R> {
myMatcher: (received: string) => R;
}
}
}
function myMatcher<T>(this: jest.MatcherUtils, received: string, expected: string): jest.CustomMatcherResult {
const pass = received === expected;
return {
pass,
message: (): string => `expected ${received} to be ${expected}`,
}
}
expect.extend({
myMatcher,
});
For a real-world example, see https://github.com/Quantum-Game/quantum-tensors/blob/master/tests/customMatchers.ts (and that the tests actually pass: https://travis-ci.com/Quantum-Game/quantum-tensors).