Skip to content

Commit

Permalink
New Feature: Spy.MAPPER
Browse files Browse the repository at this point in the history
  • Loading branch information
vikingair committed Nov 3, 2019
1 parent daa5969 commit d27d6bc
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 7 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,23 @@ spy.wasCalledWith(Spy.COMPARE(fn => {
}), 12);
```

### MAPPER (static)
```
Spy.MAPPER(from: any | any[], to: any) => SpyComparator
```
This function can be called in the same places like `Spy.COMPARE`. It is not that much
customizable but provides a nice way to evaluate mapper functions. Meaning pure
functions that return some output for given inputs. The function will be called exactly
once for each comparison, so you can even rely on site effects you might want to test,
if you want to use this for non-pure functions.
```js
spy((value: number) => ({ prop: 'here', other: value }), 12);
spy((value: number, num: number) => ({ prop: 'here', value, num }), 12);

spy.wasCalledWith(Spy.MAPPER('foo', { prop: 'here', other: 'foo' }), 12);
spy.wasCalledWith(Spy.MAPPER(['foo', 44], { prop: 'here', value: 'foo', num: 44 }), 12);
```


### configure
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spy4js",
"version": "2.8.0",
"version": "2.9.0",
"description": "Smart, compact and powerful spy test framework",
"main": "dist/cjs/spy.js",
"module": "dist/esm/spy.js",
Expand Down
2 changes: 2 additions & 0 deletions src/spy.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type SpyComparator = {
};

const COMPARE = (comparator: Comparator) => SpyComparator;
const MAPPER = (from: any | any[], to: any) => SpyComparator;
const OptionalMessageOrError = string | Error | null | undefined;

type SpyInstance = {
Expand Down Expand Up @@ -42,6 +43,7 @@ type ISpy = {
}): undefined;
IGNORE: Symbol;
COMPARE: typeof COMPARE;
MAPPER: typeof MAPPER;
on<T, K extends keyof T>(obj: T, methodName: K): SpyInstance;
mock<T, K extends keyof T>(obj: T, ...methodNames: K[]): { [P in K]: SpyInstance };
mockModule<K extends string>(moduleName: string, ...methodNames: K[]): { [P in K]: SpyInstance };
Expand Down
9 changes: 9 additions & 0 deletions src/spy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {
COMPARE,
MAPPER,
differenceOf,
forEach,
type OptionalMessageOrError,
Expand Down Expand Up @@ -724,6 +725,14 @@ class Spy {
*/
static COMPARE = COMPARE;
/**
* This static attribute can be used to test mapper
* functions inside call argument checks. It is similar to
* Spy.COMPARE, but wraps some mechanics to evaluate the result
* based on the given input.
*/
static MAPPER = MAPPER;
/**
* This static method is an alternative way to
* create a Spy which mocks the an objects attribute.
Expand Down
26 changes: 21 additions & 5 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,22 @@ const mergeArrays = (arr1: Array<any>, arr2: Array<any>): Array<any> => {
};

type Comparator = (arg: any) => boolean | void;
type ComparatorOrMapper = (arg: any) => boolean | void | string;
const SPY_COMPARE_FAILED = 'Spy.COMPARE failed';
const SPY_MAPPER_FAILED = 'Spy.MAPPER failed';
/**
* Uniquely identifiable container for spy relevant comparators.
*/
class SpyComparator {
_func: Comparator;
constructor(comparator: Comparator) {
_func: ComparatorOrMapper;
constructor(comparator: ComparatorOrMapper) {
this._func = comparator;
}
compare(arg: any): string[] | void {
if (this._func(arg) === false) return [SPY_COMPARE_FAILED];
const result = this._func(arg);
if (typeof result === 'string')
return [`${SPY_MAPPER_FAILED} [${result}]`];
if (result === false) return [SPY_COMPARE_FAILED];
}
}
/**
Expand All @@ -92,6 +97,15 @@ class SpyComparator {
const COMPARE = (comparator: Comparator): SpyComparator =>
new SpyComparator(comparator);

const MAPPER = (from: any | any[], to: any) =>
new SpyComparator((mapper: Function) => {
const result = mapper(...(Array.isArray(from) ? from : [from]));
const diff = differenceOf(result, to);
return diff
? `${serialize(result)} did not match ${serialize(to)}: ${diff}`
: undefined;
});

const __different = (type: string) => ['different ' + type];

/**
Expand Down Expand Up @@ -252,8 +266,10 @@ const differenceOf = (
const diffStr = __diffToStr(diff);
if (diff.length < 2) return diffStr;
const diffProp1 = __serializeDifferentProp(a, diff);
const lastPart = diff[diff.length - 1];
if (lastPart.indexOf(SPY_MAPPER_FAILED) === 0) return diffStr;
const info =
diff[diff.length - 1] === SPY_COMPARE_FAILED
lastPart === SPY_COMPARE_FAILED
? `called with: ${diffProp1}`
: `${diffProp1} != ${__serializeDifferentProp(b, diff)}`;
return `${diffStr} [${info}]`;
Expand All @@ -265,4 +281,4 @@ const toError = (msgOrError: OptionalMessageOrError, spyName: string) =>
? msgOrError
: new Error(msgOrError || `${spyName} was requested to throw`);

export { differenceOf, forEach, objectKeys, COMPARE, toError };
export { differenceOf, forEach, objectKeys, COMPARE, toError, MAPPER };
34 changes: 34 additions & 0 deletions test/spy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,40 @@ describe('Spy - Utils', () => {
).toThrow();
});

it('mapper testing via Spy.MAPPER', () => {
const testMapper = (state: Object, moreState: Object) => ({
prop: 'foo',
...state,
more: { ...moreState },
});
const spy = new Spy();
spy(testMapper, 42);

spy.wasCalledWith(Spy.MAPPER(undefined, { prop: 'foo', more: {} }), 42);
spy.wasCalledWith(
Spy.MAPPER(
{ some: 'stuff' },
{ prop: 'foo', some: 'stuff', more: {} }
),
42
);
spy.wasCalledWith(
Spy.MAPPER([{ some: 'stuff' }, { more: 'things' }], {
prop: 'foo',
some: 'stuff',
more: { more: 'things' },
}),
42
);
// throw because of incorrect assumption
expect(() =>
spy.wasCalledWith(
Spy.MAPPER({ prop: 'stuff' }, { prop: 'foo' }),
42
)
).toThrow();
});

it('should call the given input-functions sequentially after the spy was called', () => {
const testObj = { _key: 'testObj' };
const spy = new Spy().calls(
Expand Down
9 changes: 8 additions & 1 deletion test/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @flow
*/

import { COMPARE, differenceOf, forEach } from '../src/utils';
import { COMPARE, differenceOf, forEach, MAPPER } from '../src/utils';
import { IGNORE } from '../src/serializer';

describe('Spy - Equality', () => {
Expand Down Expand Up @@ -291,4 +291,11 @@ describe('Spy - Equality', () => {
"--> c1 / Spy.COMPARE failed [called with: 'some']"
);
});

it('applies mapper evaluation via SpyComparator MAPPER', () => {
expect(differenceOf(MAPPER(undefined, 42), () => 42)).toBe(undefined);
expect(differenceOf(MAPPER(2, 42), (num: number) => 42 + num)).toBe(
'Spy.MAPPER failed [44 did not match 42: different number]'
);
});
});

0 comments on commit d27d6bc

Please sign in to comment.