NagRock / ts-mockito

Mocking library for TypeScript
MIT License
977 stars 93 forks source link

Simple arg matches idea #137

Open Styrna opened 5 years ago

Styrna commented 5 years ago

Hi,

really like the lib for testing (kudos) but i find the writing matches for args quite complex (boiler-plate):

here is how its so far suggested (if i get it right):

verify(fooMock.someMethod(anything())).once(); 
const callArg= capture(fooMock.someMethod).last()[0];
expect(callArg.toBe(expectedValue);

i wrote a function for doing this (ofc generic) in one line like this:

verifyArg(fooMock.someMethod, arg => expect(callArg.toBe(expectedValue)).once();

here is function code:

export function verifyArg(
    method: (...methodArgs: any[]) => any,
    matcher: (...matcherArgs: any[]) => void
): MethodStubVerificator<any> {
    const args = capture(method).last();
    matcher(...args);
    return verify(method(...args.map(a => anything())));
}

here is full function with overloads for intelisense


import { anything, capture, verify } from 'ts-mockito';
import { MethodStubVerificator } from 'ts-mockito/lib/MethodStubVerificator';

export function verifyArg<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(method: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6, h: T7, i: T8, j: T9) => any, matcher: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6, h: T7, i: T8, j: T9) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1, T2, T3, T4, T5, T6, T7, T8>(method: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6, h: T7, i: T8) => any, matcher: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6, h: T7, i: T8) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1, T2, T3, T4, T5, T6, T7>(method: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6, h: T7) => any, matcher: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6, h: T7) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1, T2, T3, T4, T5, T6>(method: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6) => any, matcher: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5, g: T6) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1, T2, T3, T4, T5>(method: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5) => any, matcher: (a: T0, b: T1, c: T2, d: T3, e: T4, f: T5) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1, T2, T3, T4>(method: (a: T0, b: T1, c: T2, d: T3, e: T4) => any, matcher: (a: T0, b: T1, c: T2, d: T3, e: T4) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1, T2, T3>(method: (a: T0, b: T1, c: T2, d: T3) => any, matcher: (a: T0, b: T1, c: T2, d: T3) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1, T2>(method: (a: T0, b: T1, c: T2) => any, matcher: (a: T0, b: T1, c: T2) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0, T1>(method: (a: T0, b: T1) => any, matcher: (a: T0, b: T1) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg<T0>(method: (a: T0) => any, matcher: (a: T0) => void): MethodStubVerificator<any>; // prettier-ignore
export function verifyArg(
    method: (...methodArgs: any[]) => any,
    matcher: (...matcherArgs: any[]) => void
): MethodStubVerificator<any> {
    const args = capture(method).last();
    matcher(...args);
    return verify(method(...args.map(a => anything())));
}
NagRock commented 5 years ago

Hi @Styrna

Isn't: verify(fooMock.someMethod(expectedValue)).once(); what you need?

You don't need to use capture to verify arguments.

And if you want to check if objects are deeply equal: verify(fooMock.someMethod(deepEqual(expectedValue))).once();

Styrna commented 5 years ago

Ok sorry i oversimplified the sample. What if i dont have expected value (like its created during medoth execution) so matchers are needed as i can only match its properties by lambda.

verify(fooMock.someMethod(anything())).once(); 
const callArg= capture(fooMock.someMethod).last()[0];
expect(callArg.Property.toBe(expectedValue);

so to simply capture invokation:

verifyArg(fooMock.someMethod, arg => expect(arg.Property.toBe(expectedValue)).once();

NagRock commented 5 years ago

@Styrna ok in this case it would be nice to have matcher for example: verify(fooMock.someMethod(propsEqual(expectedValue))).once();

And expectedValue will be object just with some properties. So not all object will be matched but just provided properties. With such kind of matcher, there is no need to create new verifyArg function. It is important to keep API consistent.

Would you like to contribute and create such kind of matcher?

danielearwicker commented 4 years ago

Love this library, was expecting to find something like this:

class AnyWhereMatcher<T> extends Matcher {
    constructor(private predicate: (v: T) => boolean) {
        super();
    }

    public match(value: any): boolean {
        return this.predicate(value);
    }

    public toString(): string {
        return `anyWhere(${this.predicate})`;
    }
}

export function anyWhere<T>(predicate: (v: T) => boolean) {
    return (new AnyWhereMatcher<T>(predicate) as any) as T;
}

Is it worth me raising a PR to add it?