Skip to content

Commit

Permalink
feat: Allow overriding the default matcher
Browse files Browse the repository at this point in the history
Relates to #252.
  • Loading branch information
NiGhTTraX committed Sep 25, 2021
1 parent dc8c9e2 commit e5d27c8
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 7 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ console.log(instance(foo).bar(23)); // 'I am strong!'
- [Verifying expectations](#verifying-expectations)
- [Resetting expectations](#resetting-expectations)
- [Argument matchers](#argument-matchers)
- [Overriding default matcher](#overriding-default-matcher)
- [FAQ](#faq)
- [Why do I have to set all expectations first?](#why-do-i-have-to-set-all-expectations-first)
- [Can I mock an existing object/function?](#can-i-mock-an-existing-objectfunction)
Expand Down Expand Up @@ -306,6 +307,24 @@ console.log(instance(fn)(23, (x) => x + 1)); // 42
console.log(matcher.value?.(3)); // 4
```

### Overriding default matcher

You can override the default matcher that will be used when setting expectations with non-matcher values e.g. `42` or `{ foo: "bar" }`.

```ts
import { mock, when, instance, It, setDefaults } from 'strong-mock';

// Set the default matcher to use strict equality.
setDefaults({
matcher: (x) => It.matches((y) => y === x)
})

const fn = mock<(x: { foo: string }) => boolean>();
when(fn({ foo: "bar" })).thenReturn(true);

instance(fn)({ foo: "bar" }); // throws because different objects
```

## FAQ

### Why do I have to set all expectations first?
Expand Down
17 changes: 15 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
/* istanbul ignore file */
import { instance } from './instance/instance';
import { It } from './expectation/matcher';
import { It, Matcher } from './expectation/matcher';
import { setDefaults, StrongMockDefaults } from './mock/defaults';
import { mock } from './mock/mock';
import { reset, resetAll } from './verify/reset';
import { verify, verifyAll } from './verify/verify';
import { when } from './when/when';

export { mock, when, instance, verify, verifyAll, reset, resetAll, It };
export {
mock,
when,
instance,
verify,
verifyAll,
reset,
resetAll,
It,
setDefaults,
};

export type { StrongMockDefaults, Matcher };
49 changes: 49 additions & 0 deletions src/mock/defaults.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, it, beforeEach } from 'tdd-buffet/suite/node';
import { expect } from 'tdd-buffet/expect/jest';
import { instance, It, when } from '../index';
import { setDefaults } from './defaults';
import { mock } from './mock';

describe('defaults', () => {
beforeEach(() => {
setDefaults({});
});

it('should override the matcher for non matcher values', () => {
setDefaults({
matcher: () => It.matches(() => true),
});

const fn = mock<(x: number) => boolean>();

when(fn(1)).thenReturn(true);

expect(instance(fn)(-1)).toBeTruthy();
});

it('should not override the matcher for matcher values', () => {
setDefaults({
matcher: () => It.matches(() => true),
});

const fn = mock<(x: number) => boolean>();

when(fn(It.matches((x) => x === 1))).thenReturn(true);

expect(() => instance(fn)(-1)).toThrow();
});

it('should not stack', () => {
setDefaults({
matcher: () => It.matches(() => true),
});

setDefaults({});

const fn = mock<(x: number) => boolean>();

when(fn(1)).thenReturn(true);

expect(() => instance(fn)(-1)).toThrow();
});
});
39 changes: 39 additions & 0 deletions src/mock/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { deepEquals, Matcher } from '../expectation/matcher';

export type StrongMockDefaults = {
/**
* The matcher that will be used when one isn't specified explicitly.
*
* @param expected The non matcher expected value.
*
* @example
* StrongMock.setDefaults({
* matcher: () => It.matches(() => true)
* });
*
* when(fn('value')).thenReturn(true);
*
* instance(fn('not-value')) === true;
*/
matcher: <T>(expected: T) => Matcher;
};

const defaults: StrongMockDefaults = {
matcher: deepEquals,
};

export let currentDefaults: StrongMockDefaults = defaults;

/**
* Override strong-mock's defaults.
*
* @param newDefaults These will be applied to the library defaults. Multiple
* calls don't stack e.g. calling this with `{}` will clear any previously
* applied defaults.
*/
export const setDefaults = (newDefaults: Partial<StrongMockDefaults>): void => {
currentDefaults = {
...defaults,
...newDefaults,
};
};
11 changes: 6 additions & 5 deletions src/mock/mock.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { deepEquals, isMatcher } from '../expectation/matcher';
import { isMatcher } from '../expectation/matcher';
import { ExpectationRepository } from '../expectation/repository/expectation-repository';
import { setMockState } from './map';
import { StrongRepository } from '../expectation/repository/strong-repository';
import { StrongExpectation } from '../expectation/strong-expectation';
import {
ExpectationFactory,
RepoSideEffectPendingExpectation,
} from '../when/pending-expectation';
import { StrongExpectation } from '../expectation/strong-expectation';
import { StrongRepository } from '../expectation/repository/strong-repository';
import { currentDefaults } from './defaults';
import { setMockState } from './map';
import { createStub } from './stub';

// TODO: is it possible to return a type here that won't be assignable to T,
Expand All @@ -21,7 +22,7 @@ const strongExpectationFactory: ExpectationFactory = (
new StrongExpectation(
property,
// Wrap every non-matcher in the default matcher.
args?.map((arg) => (isMatcher(arg) ? arg : deepEquals(arg))),
args?.map((arg) => (isMatcher(arg) ? arg : currentDefaults.matcher(arg))),
returnValue
);

Expand Down

0 comments on commit e5d27c8

Please sign in to comment.