Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"node-object-hash": "^2.3.10"
},
"peerDependencies": {
"zod": "^3.21.4"
"zod": "^3.21.4",
"@anatine/zod-mock": "^3.13.4",
"@faker-js/faker": "^8.4.1"
}
}
55 changes: 48 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions src/__tests__/thenReturn_schemas/anyX-matchers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { m } from "../..";

function hi(): any {};

test("thenReturn should accept a basic a m.any matcher and return a corresponding object", () => {
const mock = m.Mock(hi);
m.when(mock).isCalled.thenReturn(m.anyArray());
expect(Array.isArray(mock())).toBe(true);

m.when(mock).isCalled.thenReturn(m.anyBoolean());
expect(typeof mock()).toBe("boolean");

m.when(mock).isCalled.thenReturn(m.anyNumber());
expect(typeof mock()).toBe("number");

m.when(mock).isCalled.thenReturn(m.anyObject());
expect(typeof mock()).toBe("object");

m.when(mock).isCalled.thenReturn(m.anyString());
expect(typeof mock()).toBe("string");

m.when(mock).isCalled.thenReturn(m.anyFunction());
expect(typeof mock()).toBe("function");

m.when(mock).isCalled.thenReturn(m.anyMap());
expect(mock()).toBeInstanceOf(Map);

m.when(mock).isCalled.thenReturn(m.anySet());
expect(mock()).toBeInstanceOf(Set);

m.when(mock).isCalled.thenReturn(m.anyNullish());
const nullishResult = mock();
expect(nullishResult === undefined || nullishResult === null).toBe(true);

m.when(mock).isCalled.thenReturn(m.anyFalsy());
const falsyResult = mock();
expect(!falsyResult).toBe(true);

m.when(mock).isCalled.thenReturn(m.anyTruthy());
const truthyResult = mock();
expect(!!truthyResult).toBeTruthy();
});
21 changes: 21 additions & 0 deletions src/__tests__/thenReturn_schemas/zod-mock.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from "zod";
import { m } from "../..";

function hi(): any {};

test("thenReturn should accept a Zod schema and return a valid object", () => {
const schema = z.object({
name: z.string(),
age: z.number(),
});

const mock = m.Mock(hi)
m.when(mock).isCalled.thenReturn(m.validates(schema));

const result = mock();
expect(result).toMatchObject({
name: expect.any(String),
age: expect.any(Number),
})
});

5 changes: 5 additions & 0 deletions src/behaviours/matcher-proxy-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ export function ProxyFactory<T>(suffix: string, content: Record<string, any>) {
},
{
get(target, prop) {
// @ts-expect-error - I don't know how to fix this yet
if (target === "mockitSuffix") {
return () => suffix;
}

// @ts-expect-error - I don't know how to fix this yet
return target[prop];
},
Expand Down
3 changes: 3 additions & 0 deletions src/behaviours/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ export const instanceOf = <T, U>(original: U) => {
return ProxyFactory<T>("instanceOf", { class: original });
};

export type anyWhat = "string" | "object" | "number" | "boolean" | "array" | "function" | "nullish" | "falsy" | "truthy" | "map" | "set";

/**
*
* @param regexp a regular expression that the string should match
Expand Down Expand Up @@ -427,3 +429,4 @@ export type PartialDeep<T> = T extends (...args: any[]) => any
export type PartialDeepObject<ObjectType extends object> = {
[KeyType in keyof ObjectType]?: PartialDeep<ObjectType[KeyType]>;
};

56 changes: 52 additions & 4 deletions src/mocks/mockFunction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { z } from "zod";
import { generateMock } from "@anatine/zod-mock";

import { Call } from "../types";
import { hasher } from "../hasher";
import { Behaviours, NewBehaviourParam } from "../behaviours/behaviours";
import { compare } from "../argsComparisons/compare";
import { z, ZodSchema } from "zod";
import { anyWhat } from "../behaviours/matchers";

/**
* This is the function mock, it is taking the place of the function that we want to mock.
Expand Down Expand Up @@ -50,7 +52,7 @@ export function mockFunction<T extends (...args: any[]) => any>(
case Behaviours.Call:
return customBehaviour.behaviour.callback(...callArgs);
case Behaviours.Return:
return customBehaviour.behaviour.returnedValue;
return handleThenReturn(customBehaviour.behaviour.returnedValue);
case Behaviours.Resolve:
return Promise.resolve(customBehaviour.behaviour.resolvedValue);
case Behaviours.Reject:
Expand Down Expand Up @@ -82,7 +84,7 @@ export function mockFunction<T extends (...args: any[]) => any>(
...callArgs
);
case Behaviours.Return:
return constructBasedCustomBehaviour.behaviour.returnedValue;
return handleThenReturn(constructBasedCustomBehaviour.behaviour.returnedValue);
case Behaviours.Resolve:
return Promise.resolve(
constructBasedCustomBehaviour.behaviour.resolvedValue
Expand All @@ -109,7 +111,7 @@ export function mockFunction<T extends (...args: any[]) => any>(
case Behaviours.Call:
return defaultBehaviour.callback(...callArgs);
case Behaviours.Return:
return defaultBehaviour.returnedValue;
return handleThenReturn(defaultBehaviour.returnedValue);
case Behaviours.Resolve:
return Promise.resolve(defaultBehaviour.resolvedValue);
case Behaviours.Reject:
Expand Down Expand Up @@ -172,3 +174,49 @@ export function mockFunction<T extends (...args: any[]) => any>(
},
});
}


function handleThenReturn(returnedValue: unknown) {
if (narrowToZodValidationMatcher(returnedValue)) {
return generateMock(returnedValue.schema);
}

if (narrowToAnyMatcher(returnedValue)) {
switch(returnedValue.what) {
case "string":
return generateMock(z.string());
case "object":
return generateMock(z.object({}));
case "number":
return generateMock(z.number());
case "boolean":
return generateMock(z.boolean());
case "array":
return generateMock(z.array(z.any()));
case "falsy":
return generateMock(z.union([z.literal(0), z.literal(false), z.literal("")]));
case "truthy":
return generateMock(z.union([z.literal(1), z.literal(true)]));
case "function":
return () => {};
case "map":
return generateMock(z.map(z.any(), z.any()));
case "set":
return new Set([1, 2, 3]);
case "nullish":
return generateMock(z.union([z.null(), z.undefined()]));
default:
throw new Error(`Invalid any matcher ${returnedValue.what}`);
}
}

return returnedValue;
}

function narrowToZodValidationMatcher(value: any): value is { schema: ZodSchema } {
return typeof value === "object" && value !== null && "mockit__isSchema" in value && value.mockit__isSchema;
}

function narrowToAnyMatcher(value: any): value is { what: anyWhat} {
return typeof value === "object" && value !== null && "mockit__any" in value;
}