Skip to content

Commit efc6f0e

Browse files
committed
feat: add vitest utils
1 parent 7d6fec9 commit efc6f0e

File tree

9 files changed

+143
-5
lines changed

9 files changed

+143
-5
lines changed

apps/example-app/src/app/examples/05-component-provider.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, expect } from 'vitest';
22
import { TestBed } from '@angular/core/testing';
33
import { render, screen } from '@testing-library/angular';
4-
import { provideMock, Mock, createMock } from '@testing-library/angular/jest-utils';
4+
import { provideMock, Mock, createMock } from '@testing-library/angular/vitest-utils';
55
import userEvent from '@testing-library/user-event';
66

77
import { ComponentWithProviderComponent, CounterService } from './05-component-provider';
@@ -32,7 +32,7 @@ test('renders the current value and can increment and decrement', async () => {
3232
expect(valueControl).toHaveTextContent('1');
3333
});
3434

35-
test('renders the current value and can increment and decrement with a mocked jest-utils service', async () => {
35+
test('renders the current value and can increment and decrement with a mocked vitest-utils service', async () => {
3636
const user = userEvent.setup();
3737

3838
const counter = createMock(CounterService);
@@ -64,7 +64,7 @@ test('renders the current value and can increment and decrement with a mocked je
6464
expect(valueControl).toHaveTextContent('60');
6565
});
6666

67-
test('renders the current value and can increment and decrement with provideMocked from jest-utils', async () => {
67+
test('renders the current value and can increment and decrement with provideMocked from vitest-utils', async () => {
6868
const user = userEvent.setup();
6969

7070
await render(ComponentWithProviderComponent, {

apps/example-app/src/app/examples/12-service-component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { of } from 'rxjs';
22
import { test, expect, vi } from 'vitest';
33
import { render, screen } from '@testing-library/angular';
4-
import { createMock } from '@testing-library/angular/jest-utils';
4+
import { createMock } from '@testing-library/angular/vitest-utils';
55

66
import { Customer, CustomersComponent, CustomersService } from './12-service-component';
77

apps/example-app/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"target": "es2020",
77
"paths": {
88
"@testing-library/angular": ["projects/testing-library"],
9-
"@testing-library/angular/jest-utils": ["projects/testing-library/jest-utils"]
9+
"@testing-library/angular/jest-utils": ["projects/testing-library/jest-utils"],
10+
"@testing-library/angular/vitest-utils": ["projects/testing-library/vitest-utils"]
1011
}
1112
},
1213
"angularCompilerOptions": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './src/public_api';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Type, Provider } from '@angular/core';
2+
import { vi, type Mock as VitestMock } from 'vitest';
3+
4+
export type Mock<T> = T & { [K in keyof T]: T[K] & VitestMock };
5+
6+
export function createMock<T>(type: Type<T>): Mock<T> {
7+
const mock: any = {};
8+
9+
function mockFunctions(proto: any) {
10+
if (!proto) {
11+
return;
12+
}
13+
14+
for (const prop of Object.getOwnPropertyNames(proto)) {
15+
if (prop === 'constructor') {
16+
continue;
17+
}
18+
19+
const descriptor = Object.getOwnPropertyDescriptor(proto, prop);
20+
if (typeof descriptor?.value === 'function') {
21+
mock[prop] = vi.fn();
22+
}
23+
}
24+
25+
mockFunctions(Object.getPrototypeOf(proto));
26+
}
27+
28+
mockFunctions(type.prototype);
29+
30+
return mock;
31+
}
32+
33+
export function createMockWithValues<T, K extends keyof T>(type: Type<T>, values: Partial<Record<K, T[K]>>): Mock<T> {
34+
const mock = createMock(type);
35+
36+
Object.entries(values).forEach(([field, value]) => {
37+
(mock as any)[field] = value;
38+
});
39+
40+
return mock;
41+
}
42+
43+
export function provideMock<T>(type: Type<T>): Provider {
44+
return {
45+
provide: type,
46+
useValue: createMock(type),
47+
};
48+
}
49+
50+
export function provideMockWithValues<T, K extends keyof T>(type: Type<T>, values: Partial<Record<K, T[K]>>): Provider {
51+
return {
52+
provide: type,
53+
useValue: createMockWithValues(type, values),
54+
};
55+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './create-mock';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/*
2+
* Public API Surface of testing-library
3+
*/
4+
5+
export * from './lib';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Component, inject } from '@angular/core';
2+
import { TestBed } from '@angular/core/testing';
3+
import { test, expect, vi } from 'vitest';
4+
import { fireEvent, render, screen } from '../../src/public_api';
5+
import { createMock, provideMock, provideMockWithValues } from '../src/public_api';
6+
7+
class FixtureService {
8+
constructor(private foo: string, public bar: string) {}
9+
10+
print() {
11+
console.log(this.foo, this.bar);
12+
}
13+
14+
concat() {
15+
return this.foo + this.bar;
16+
}
17+
}
18+
19+
@Component({
20+
selector: 'atl-fixture',
21+
template: ` <button (click)="print()">Print</button> `,
22+
})
23+
class FixtureComponent {
24+
private service = inject(FixtureService);
25+
26+
print() {
27+
this.service.print();
28+
}
29+
}
30+
31+
test('mocks all functions', () => {
32+
const mock = createMock(FixtureService);
33+
expect(mock.print.mock).toBeDefined();
34+
});
35+
36+
test('provides a mock service', async () => {
37+
await render(FixtureComponent, {
38+
providers: [provideMock(FixtureService)],
39+
});
40+
const service = TestBed.inject(FixtureService);
41+
42+
fireEvent.click(screen.getByText('Print'));
43+
expect(service.print).toHaveBeenCalledTimes(1);
44+
});
45+
46+
test('provides a mock service with values', async () => {
47+
await render(FixtureComponent, {
48+
providers: [
49+
provideMockWithValues(FixtureService, {
50+
bar: 'value',
51+
concat: vi.fn(() => 'a concatenated value'),
52+
}),
53+
],
54+
});
55+
56+
const service = TestBed.inject(FixtureService);
57+
58+
fireEvent.click(screen.getByText('Print'));
59+
60+
expect(service.bar).toEqual('value');
61+
expect(service.concat()).toEqual('a concatenated value');
62+
expect(service.print).toHaveBeenCalled();
63+
});
64+
65+
test('is possible to write a mock implementation', async () => {
66+
await render(FixtureComponent, {
67+
providers: [provideMock(FixtureService)],
68+
});
69+
70+
const service = TestBed.inject(FixtureService);
71+
72+
fireEvent.click(screen.getByText('Print'));
73+
expect(service.print).toHaveBeenCalled();
74+
});

0 commit comments

Comments
 (0)