fix: fix some tests
This commit is contained in:
@@ -4,10 +4,12 @@ import { vi } from 'vitest';
|
||||
|
||||
// Create retriable observable stream to test retry / retryWhen. Credits to:
|
||||
// https://stackoverflow.com/questions/51399819/how-to-create-a-mock-observable-to-test-http-rxjs-retry-retrywhen-in-angular
|
||||
export const createRetriableStream = (...resp$: any): Observable<any> => {
|
||||
const fetchData = vi.fn()('fetchData');
|
||||
export const createRetriableStream = (...resp$: any[]): Observable<any> => {
|
||||
const fetchData = vi.fn();
|
||||
|
||||
fetchData.mockReturnValues(...resp$);
|
||||
for (const r of resp$) {
|
||||
fetchData.mockReturnValueOnce(r);
|
||||
}
|
||||
|
||||
return of(null).pipe(switchMap((_) => fetchData()));
|
||||
};
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { getTestBed } from '@/testing/testbed';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting,
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
import { TestBed } from '@/testing/testbed';
|
||||
import { DOCUMENT } from 'oidc-client-rx/dom';
|
||||
import 'reflect-metadata';
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
TestBed.initTestEnvironment([
|
||||
{
|
||||
teardown: { destroyAfterEach: false },
|
||||
}
|
||||
);
|
||||
provide: DOCUMENT,
|
||||
useValue: document,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { Provider } from 'injection-js';
|
||||
|
||||
export function mockClass<T>(obj: new (...args: any[]) => T): any {
|
||||
export function mockClass<T>(
|
||||
obj: new (...args: any[]) => T
|
||||
): new (
|
||||
...args: any[]
|
||||
) => T {
|
||||
const keys = Object.getOwnPropertyNames(obj.prototype);
|
||||
const allMethods = keys.filter((key) => {
|
||||
try {
|
||||
@@ -14,9 +18,16 @@ export function mockClass<T>(obj: new (...args: any[]) => T): any {
|
||||
const mockedClass = class T {};
|
||||
|
||||
for (const method of allMethods) {
|
||||
(mockedClass.prototype as any)[method] = (): void => {
|
||||
return;
|
||||
};
|
||||
const mockImplementation = Reflect.getMetadata(
|
||||
'mock:implementation',
|
||||
obj.prototype,
|
||||
method
|
||||
);
|
||||
(mockedClass.prototype as any)[method] =
|
||||
mockImplementation ??
|
||||
((): any => {
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
for (const method of allProperties) {
|
||||
@@ -28,12 +39,15 @@ export function mockClass<T>(obj: new (...args: any[]) => T): any {
|
||||
});
|
||||
}
|
||||
|
||||
return mockedClass;
|
||||
return mockedClass as any;
|
||||
}
|
||||
|
||||
export function mockProvider<T>(obj: new (...args: any[]) => T): Provider {
|
||||
export function mockProvider<T>(
|
||||
obj: new (...args: any[]) => T,
|
||||
token?: any
|
||||
): Provider {
|
||||
return {
|
||||
provide: obj,
|
||||
provide: token ?? obj,
|
||||
useClass: mockClass(obj),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import type { Provider } from 'injection-js';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { AbstractRouter, type Navigation, type UrlTree } from 'oidc-client-rx';
|
||||
|
||||
export class MockRouter extends AbstractRouter {
|
||||
dom = new JSDOM('', {
|
||||
url: 'http://localhost',
|
||||
});
|
||||
|
||||
navigation: Navigation = {
|
||||
id: 1,
|
||||
extras: {},
|
||||
initialUrl: new URL('https://localhost/'),
|
||||
extractedUrl: new URL('https://localhost/'),
|
||||
initialUrl: this.parseUrl(this.dom.window.location.href),
|
||||
extractedUrl: this.parseUrl(this.dom.window.location.href),
|
||||
trigger: 'imperative',
|
||||
previousNavigation: null,
|
||||
};
|
||||
|
||||
navigateByUrl(url: string): void {
|
||||
const prevNavigation = this.navigation;
|
||||
this.dom.reconfigure({
|
||||
url: new URL(url, this.dom.window.location.href).href,
|
||||
});
|
||||
this.navigation = {
|
||||
id: prevNavigation.id + 1,
|
||||
extras: {},
|
||||
initialUrl: prevNavigation.initialUrl,
|
||||
extractedUrl: new URL(url),
|
||||
extractedUrl: this.parseUrl(this.dom.window.location.href),
|
||||
trigger: prevNavigation.trigger,
|
||||
previousNavigation: prevNavigation,
|
||||
};
|
||||
@@ -26,7 +34,8 @@ export class MockRouter extends AbstractRouter {
|
||||
return this.navigation;
|
||||
}
|
||||
parseUrl(url: string): UrlTree {
|
||||
return new URL(url);
|
||||
const u = new URL(url, this.dom.window.location.href);
|
||||
return `${u.pathname}${u.search}${u.hash}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,20 +35,80 @@ export function mockImplementationWhenArgsEqual<M extends MockInstance<any>>(
|
||||
});
|
||||
}
|
||||
|
||||
export function mockImplementationWhenArgs<M extends MockInstance<any>>(
|
||||
mockInstance: M,
|
||||
whenArgs: (
|
||||
...args: Parameters<M extends MockInstance<infer T> ? T : never>
|
||||
) => boolean,
|
||||
implementation: Exclude<ReturnType<M['getMockImplementation']>, undefined>
|
||||
): M {
|
||||
const spyImpl = mockInstance.getMockImplementation()!;
|
||||
type Procedure = (...args: any[]) => any;
|
||||
type Methods<T> = keyof {
|
||||
[K in keyof T as T[K] extends Procedure ? K : never]: T[K];
|
||||
};
|
||||
type Classes<T> = {
|
||||
[K in keyof T]: T[K] extends new (...args: any[]) => any ? K : never;
|
||||
}[keyof T] &
|
||||
(string | symbol);
|
||||
|
||||
export type MockInstanceWithOrigin<M extends Procedure> = MockInstance<M> & {
|
||||
getOriginImplementation?: () => any;
|
||||
};
|
||||
|
||||
export function spyOnWithOrigin<
|
||||
T,
|
||||
M extends Classes<Required<T>> | Methods<Required<T>>,
|
||||
>(
|
||||
obj: T,
|
||||
methodName: M
|
||||
): Required<T>[M] extends {
|
||||
new (...args: infer A): infer R;
|
||||
}
|
||||
? MockInstanceWithOrigin<(this: R, ...args: A) => R>
|
||||
: T[M] extends Procedure
|
||||
? MockInstanceWithOrigin<T[M]>
|
||||
: never {
|
||||
let currentObj = obj;
|
||||
let origin:
|
||||
| (Required<T>[M] extends {
|
||||
new (...args: infer A): infer R;
|
||||
}
|
||||
? (this: R, ...args: A) => R
|
||||
: T[M] extends Procedure
|
||||
? T[M]
|
||||
: never)
|
||||
| undefined;
|
||||
while (currentObj) {
|
||||
origin = currentObj[methodName] as any;
|
||||
if (origin) {
|
||||
break;
|
||||
}
|
||||
currentObj = Object.getPrototypeOf(currentObj);
|
||||
}
|
||||
|
||||
const spy = vi.spyOn(obj, methodName as any) as Required<T>[M] extends {
|
||||
new (...args: infer A): infer R;
|
||||
}
|
||||
? MockInstanceWithOrigin<(this: R, ...args: A) => R>
|
||||
: T[M] extends Procedure
|
||||
? MockInstanceWithOrigin<T[M]>
|
||||
: never;
|
||||
|
||||
spy.getOriginImplementation = () => origin;
|
||||
|
||||
return spy;
|
||||
}
|
||||
|
||||
export function mockImplementationWhenArgs<T extends Procedure = Procedure>(
|
||||
mockInstance: MockInstance<T> & { getOriginImplementation?: () => T },
|
||||
whenArgs: (...args: Parameters<T>) => boolean,
|
||||
implementation: T
|
||||
): MockInstance<T> {
|
||||
const spyImpl =
|
||||
mockInstance.getMockImplementation() ??
|
||||
mockInstance.getOriginImplementation?.();
|
||||
|
||||
return mockInstance.mockImplementation((...args) => {
|
||||
if (isEqual(args, whenArgs)) {
|
||||
if (whenArgs(...args)) {
|
||||
return implementation(...args);
|
||||
}
|
||||
return spyImpl?.(...args);
|
||||
if (spyImpl) {
|
||||
return spyImpl(...args);
|
||||
}
|
||||
throw new Error('Mock implementation not defined for these arguments.');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,46 +118,37 @@ export function mockImplementationWhenArgs<M extends MockInstance<any>>(
|
||||
export function spyOnProperty<T, K extends keyof T>(
|
||||
obj: T,
|
||||
propertyKey: K,
|
||||
accessType: 'get' | 'set' = 'get',
|
||||
mockImplementation?: any
|
||||
accessType: 'get' | 'set' = 'get'
|
||||
) {
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(obj, propertyKey);
|
||||
|
||||
if (!originalDescriptor) {
|
||||
throw new Error(
|
||||
`Property ${String(propertyKey)} does not exist on the object.`
|
||||
);
|
||||
const ownDescriptor = Object.getOwnPropertyDescriptor(obj, propertyKey);
|
||||
let finalDescriptor: PropertyDescriptor | undefined;
|
||||
let currentObj = obj;
|
||||
while (currentObj) {
|
||||
finalDescriptor = Object.getOwnPropertyDescriptor(currentObj, propertyKey);
|
||||
if (finalDescriptor) {
|
||||
break;
|
||||
}
|
||||
currentObj = Object.getPrototypeOf(currentObj);
|
||||
}
|
||||
|
||||
const spy = vi.fn();
|
||||
|
||||
let value: T[K] | undefined;
|
||||
|
||||
if (accessType === 'get') {
|
||||
Object.defineProperty(obj, propertyKey, {
|
||||
get: mockImplementation
|
||||
? () => {
|
||||
value = mockImplementation();
|
||||
return value;
|
||||
}
|
||||
: spy,
|
||||
get: spy,
|
||||
configurable: true,
|
||||
});
|
||||
} else if (accessType === 'set') {
|
||||
Object.defineProperty(obj, propertyKey, {
|
||||
set: mockImplementation
|
||||
? (next) => {
|
||||
value = next;
|
||||
}
|
||||
: spy,
|
||||
set: spy,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 恢复原始属性
|
||||
spy.mockRestore = () => {
|
||||
if (originalDescriptor) {
|
||||
Object.defineProperty(obj, propertyKey, originalDescriptor);
|
||||
if (ownDescriptor) {
|
||||
Object.defineProperty(obj, propertyKey, ownDescriptor);
|
||||
} else {
|
||||
delete obj[propertyKey];
|
||||
}
|
||||
|
||||
@@ -13,21 +13,33 @@ export interface TestModuleMetadata {
|
||||
}
|
||||
|
||||
export class TestBed {
|
||||
static environmentInjector?: Injector;
|
||||
private injector: ReflectiveInjector;
|
||||
private providers: Provider[] = [];
|
||||
private imports: Injector[] = [];
|
||||
|
||||
constructor(metadata: TestModuleMetadata = {}) {
|
||||
constructor(
|
||||
metadata: TestModuleMetadata = {},
|
||||
environmentInjector?: Injector
|
||||
) {
|
||||
const providers = metadata.providers ?? [];
|
||||
const imports = metadata.imports ?? [];
|
||||
this.injector = ReflectiveInjector.resolveAndCreate(providers);
|
||||
this.injector = ReflectiveInjector.resolveAndCreate(
|
||||
providers,
|
||||
environmentInjector
|
||||
);
|
||||
this.imports = imports.map((importFn) => importFn(this.injector));
|
||||
}
|
||||
|
||||
static #instance?: TestBed;
|
||||
|
||||
static initTestEnvironment(providers: Provider[] = []) {
|
||||
TestBed.environmentInjector =
|
||||
ReflectiveInjector.resolveAndCreate(providers);
|
||||
}
|
||||
|
||||
static configureTestingModule(metadata: TestModuleMetadata = {}) {
|
||||
const newTestBed = new TestBed(metadata);
|
||||
const newTestBed = new TestBed(metadata, TestBed.environmentInjector);
|
||||
TestBed.#instance = newTestBed;
|
||||
|
||||
return newTestBed;
|
||||
|
||||
Reference in New Issue
Block a user