Skip to content

Commit

Permalink
feat(mock): support asynchronous of response (#1686)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk authored Nov 9, 2023
1 parent 66e612d commit d7980db
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 42 deletions.
4 changes: 1 addition & 3 deletions _mock/user.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { MockStatusError, MockRequest } from '@delon/mock';
import { MockStatusError, MockRequest, r } from '@delon/mock';
import type { NzSafeAny } from 'ng-zorro-antd/core/types';
// import * as Mock from 'mockjs';

const r = (min: number, max: number): number => Math.floor(Math.random() * (max - min + 1) + min);

export const USERS = {
// 支持值为 Object 和 Array
'GET /users': (req: MockRequest) => {
Expand Down
20 changes: 17 additions & 3 deletions packages/mock/docs/rule.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const USERS = {

## Value

Supports three types: `Object`, `Array`, `(req: MockRequest) => any`.
Supports three types: `Object`, `Array`, `(req: MockRequest) => any | Observable<any> | Promise<any>`.

```ts
import { MockStatusError } from '@delon/mock';
Expand All @@ -51,7 +51,14 @@ export const USERS = {
// Support HttpResponse
'/http': (req: MockRequest) => new HttpResponse({ body: 'Body', headers: new HttpHeaders({ 'token': '1' }) }),
// Send Status Error
'/404': () => { throw new MockStatusError(404); }
'/404': () => { throw new MockStatusError(404); },
// Support Observable
'/obs': () => of(1),
// Support Promise
'/promise': async () => {
await delay(10);
return 1;
}
};
```

Expand Down Expand Up @@ -88,7 +95,14 @@ export const USERS = {
// Send Status Error
'/404': () => { throw new MockStatusError(404); },
// Regular expressions need to be wrapped with `()`
'/data/(.*)': (req: MockRequest) => req
'/data/(.*)': (req: MockRequest) => req,
// Support Observable
'/obs': () => of(1),
// Support Promise
'/promise': async () => {
await delay(10);
return 1;
}
};
```

Expand Down
20 changes: 17 additions & 3 deletions packages/mock/docs/rule.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const USERS = {

## Value 响应内容

响应内容支持三种类型:`Object``Array``(req: MockRequest) => any`
响应内容支持三种类型:`Object``Array``(req: MockRequest) => any | Observable<any> | Promise<any>`

```ts
import { MockStatusError } from '@delon/mock';
Expand All @@ -51,7 +51,14 @@ export const USERS = {
// 支持返回完整的 HttpResponse
'/http': (req: MockRequest) => new HttpResponse({ body: 'Body', headers: new HttpHeaders({ 'token': '1' }) }),
// 发送 Status 错误
'/404': () => { throw new MockStatusError(404); }
'/404': () => { throw new MockStatusError(404); },
// 支持 Observable
'/obs': () => of(1),
// 支持 Promise
'/promise': async () => {
await delay(10);
return 1;
}
};
```

Expand Down Expand Up @@ -88,7 +95,14 @@ export const USERS = {
// 发送 Status 错误
'/404': () => { throw new MockStatusError(404); },
// 使用 () 表示:正则表达式
'/data/(.*)': (req: MockRequest) => req
'/data/(.*)': (req: MockRequest) => req,
// 支持 Observable
'/obs': () => of(1),
// 支持 Promise
'/promise': async () => {
await delay(10);
return 1;
}
};
```

Expand Down
1 change: 1 addition & 0 deletions packages/mock/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './src/status.error';
export * from './src/mock.service';
export * from './src/mock.interceptor';
export * from './src/mock.module';
export * from './src/utils';
9 changes: 6 additions & 3 deletions packages/mock/src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { HttpRequest } from '@angular/common/http';
import type { HttpRequest } from '@angular/common/http';
import type { Observable } from 'rxjs';

export type MockCallback = any | Observable<any> | Promise<any>;

export class MockOptions {
data?: any;
Expand All @@ -16,7 +19,7 @@ export interface MockCachedRule {

segments: string[];

callback(req: MockRequest): any;
callback(req: MockRequest): MockCallback;
}

export interface MockRule {
Expand All @@ -29,7 +32,7 @@ export interface MockRule {
/** 路由参数 */
params?: any;

callback(req: MockRequest): any;
callback(req: MockRequest): MockCallback;
}

export interface MockRequest {
Expand Down
17 changes: 16 additions & 1 deletion packages/mock/src/mock.interceptor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Component, NgModule, Type } from '@angular/core';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router, RouterModule } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable, map } from 'rxjs';
import { Observable, lastValueFrom, map, of } from 'rxjs';

import * as Mock from 'mockjs';

Expand All @@ -23,6 +23,7 @@ import { AlainMockConfig, ALAIN_CONFIG } from '@delon/util/config';
import { MockRequest } from './interface';
import { DelonMockModule } from './mock.module';
import { MockStatusError } from './status.error';
import { delay, r } from './utils';

const USER_LIST = { users: [1, 2], a: 0 };
const DATA = {
Expand All @@ -41,6 +42,11 @@ const DATA = {
},
'/500': () => {
throw new Error('500');
},
'/obs': () => of(r(1, 1)),
'/promise': async () => {
await delay(10);
return 'a';
}
}
};
Expand Down Expand Up @@ -198,6 +204,15 @@ describe('mock: interceptor', () => {
done();
});
});
it('should be return a observable', () => {
http.get('/obs').subscribe(res => {
expect(res).toBe(1);
});
});
it('should be return a promise', async () => {
const res = await lastValueFrom(http.get('/promise'));
expect(res).toBe('a');
});
});

describe('[disabled log]', () => {
Expand Down
66 changes: 37 additions & 29 deletions packages/mock/src/mock.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
HTTP_INTERCEPTORS
} from '@angular/common/http';
import { Injectable, Injector } from '@angular/core';
import { Observable, of, throwError, delay } from 'rxjs';
import { Observable, of, throwError, delay, isObservable, from, map, switchMap } from 'rxjs';

import { deepCopy } from '@delon/util/other';

Expand Down Expand Up @@ -42,7 +42,7 @@ export class MockInterceptor implements HttpInterceptor {
return next.handle(req);
}

let res: any;
let res$: Observable<any>;
switch (typeof rule!.callback) {
case 'function':
const mockRequest: MockRequest = {
Expand Down Expand Up @@ -73,40 +73,48 @@ export class MockInterceptor implements HttpInterceptor {
req.headers.keys().forEach(key => (mockRequest.headers[key] = req.headers.get(key)));

try {
res = rule!.callback.call(this, mockRequest);
const fnRes = rule!.callback.call(this, mockRequest);
res$ = isObservable(fnRes) ? fnRes : from(Promise.resolve(fnRes));
} catch (e: any) {
res = new HttpErrorResponse({
url: req.url,
headers: req.headers,
status: e instanceof MockStatusError ? e.status : 400,
statusText: e.statusText || 'Unknown Error',
error: e.error
});
res$ = of(
new HttpErrorResponse({
url: req.url,
headers: req.headers,
status: e instanceof MockStatusError ? e.status : 400,
statusText: e.statusText || 'Unknown Error',
error: e.error
})
);
}
break;
default:
res = rule!.callback;
res$ = of(rule!.callback);
break;
}

if (!(res instanceof HttpResponseBase)) {
res = new HttpResponse({
status: 200,
url: req.url,
body: res
});
}

if (res.body) {
res.body = deepCopy(res.body);
}

if (config.log) {
console.log(`%c👽${req.method}->${req.urlWithParams}->request`, 'background:#000;color:#bada55', req);
console.log(`%c👽${req.method}->${req.urlWithParams}->response`, 'background:#000;color:#bada55', res);
}

const res$ = res instanceof HttpErrorResponse ? throwError(() => res) : of(res);
res$ = res$.pipe(
map(res =>
res instanceof HttpResponseBase
? res
: new HttpResponse({
status: 200,
url: req.url,
body: deepCopy(res)
})
),
map((res: HttpResponseBase) => {
const anyRes: any = res;
if (anyRes.body) {
anyRes.body = deepCopy(anyRes.body);
}
if (config.log) {
console.log(`%c👽${req.method}->${req.urlWithParams}->request`, 'background:#000;color:#bada55', req);
console.log(`%c👽${req.method}->${req.urlWithParams}->response`, 'background:#000;color:#bada55', res);
}
return res;
}),
switchMap((res: HttpResponseBase) => (res instanceof HttpErrorResponse ? throwError(() => res) : of(res)))
);

if (config.executeOtherInterceptors) {
const interceptors = this.injector.get(HTTP_INTERCEPTORS, []);
Expand Down
13 changes: 13 additions & 0 deletions packages/mock/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Used to simulate delays
*/
export function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

/**
* Return a random number
*/
export function r(min = 1, max = 100): number {
return Math.floor(Math.random() * (max - min + 1) + min);
}

0 comments on commit d7980db

Please sign in to comment.