Skip to content

Instantly share code, notes, and snippets.

@zirkelc
Created May 18, 2022 12:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zirkelc/1ebb4e3d026bb85b6cbd4ae21174f83c to your computer and use it in GitHub Desktop.
Save zirkelc/1ebb4e3d026bb85b6cbd4ae21174f83c to your computer and use it in GitHub Desktop.
Custom matcher in Jest
import fetch from 'cross-fetch';
type Todo = {
id: number;
userId: number;
title: string;
completed: boolean;
};
interface CustomMatchers<R = unknown> {
toMatchTodo(todo?: Partial<Todo> | Array<Partial<Todo>> | undefined): R;
}
declare global {
namespace jest {
interface Expect extends CustomMatchers {}
interface Matchers<R> extends CustomMatchers<R> {}
interface InverseAsymmetricMatchers extends CustomMatchers {}
}
}
expect.extend({
toMatchTodo(received, expected) {
// define Todo object structure with objectContaining
const expectTodoObject = (todo?: Todo) =>
expect.objectContaining({
id: todo?.id ?? expect.any(Number),
userId: todo?.userId ?? expect.any(Number),
title: todo?.title ?? expect.any(String),
completed: todo?.completed ?? expect.any(Boolean),
});
// define Todo array with arrayContaining and re-use expectTodoObject
const expectTodoArray = (todos: Array<Todo>) =>
todos.length === 0
? // in case an empty array is passed
expect.arrayContaining([expectTodoObject()])
: // in case an array of Todos is passed
expect.arrayContaining(todos.map(expectTodoObject));
// expected can either be an array or an object
const expectedResult = Array.isArray(expected) ? expectTodoArray(expected) : expectTodoObject(expected);
// equality check for received todo and expected todo
const pass = this.equals(received, expectedResult);
if (pass) {
return {
message: () =>
`Expected: ${this.utils.printExpected(expectedResult)}\nReceived: ${this.utils.printReceived(received)}`,
pass: true,
};
}
return {
message: () =>
`Expected: ${this.utils.printExpected(expectedResult)}\nReceived: ${this.utils.printReceived(
received,
)}\n\n${this.utils.diff(expectedResult, received)}`,
pass: false,
};
},
});
describe('Todo API', () => {
test('Get Todo By ID', async () => {
const todo = await fetch(`https://jsonplaceholder.typicode.com/todos/1`).then((r) => r.json());
// match any Todo item
expect(todo).toMatchTodo();
// match specific Todo item
expect(todo).toMatchTodo({
id: 1,
userId: 1,
title: 'delectus aut autem',
completed: false,
});
// fails because of missing properties (id, userId, completed)
expect({ title: 'delectus aut autem' }).toMatchTodo();
});
test('List all Todos ', async () => {
const todos = await fetch(`https://jsonplaceholder.typicode.com/todos`).then((r) => r.json());
const todo1 = {
id: 1,
userId: 1,
title: 'delectus aut autem',
completed: false,
};
const todo2 = {
id: 2,
userId: 1,
title: 'quis ut nam facilis et officia qui',
completed: false,
};
// match any array of Todos
expect(todos).toMatchTodo([]);
// match array of Todos with specific items
expect(todos).toMatchTodo([todo1, todo2]);
// fails because of missing todo item (todo2)
expect([todo1]).toMatchTodo([todo1, todo2]);
});
test('Create Todo', async () => {
const newTodo = {
userId: 1,
title: 'quis ut nam facilis et officia qui',
completed: false,
};
const todo = await fetch(`https://jsonplaceholder.typicode.com/todos`, {
method: 'POST',
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(newTodo),
}).then((r) => r.json());
// match any Todo item
expect(todo).toMatchTodo();
// match specific newTodo item, but match any ID property as it's generated by the server
expect(todo).toMatchTodo(newTodo);
// fails because of id is string instead of number
expect({ ...todo, id: '2' }).toMatchTodo(newTodo);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment