Skip to content

Commit

Permalink
feat(module:mock): add executeOtherInterceptors config (#321)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk authored Dec 15, 2018
1 parent 025282c commit d77e8e9
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 93 deletions.
3 changes: 2 additions & 1 deletion packages/mock/docs/getting-started.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ The `forRoot` parameters:
| `[delay]` | `number` | `300` | Request delay, unit is milliseconds |
| `[force]` | `boolean` | `false` | Whether to force all requests to Mock, `true` means to return a 404 error directly when the requested URL does not exist, `false` means to send a real HTTP request when the request is missed |
| `[log]` | `boolean` | `true` | Whether to print Mock request information, make up for the browser without Network information; it will output [👽Mock] when hit |
| `[executeOtherInterceptors]` | `boolean` | `true` | Whether continue to call other interceptor `intercept` method after mock rule hit |

> Submodules need to import `forChild`.
> **Lazy modules** need to import `forChild`, You can import `forChild` in the `SharedModule`.
3 changes: 2 additions & 1 deletion packages/mock/docs/getting-started.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ const MOCKMODULE = !environment.production ? [ DelonMockModule.forRoot({ data: M
| `[delay]` | `number` | `300` | 请求延迟,单位:毫秒 |
| `[force]` | `boolean` | `false` | 是否强制所有请求都Mock,`true` 表示当请求的URL不存在时直接返回 404 错误,`false` 表示未命中时发送真实HTTP请求 |
| `[log]` | `boolean` | `true` | 是否打印 Mock 请求信息,弥补浏览器无Network信息;当请求经过 Mock 会接收【👽Mock】 |
| `[executeOtherInterceptors]` | `boolean` | `true` | 是否拦截命中后继续调用后续拦截器的 `intercept` 方法 |

> 若子模块还需要使用确保HTTP拦截器有效,一般可以直接在 SharedModule 直接使用 `forChild`
> **懒模块**还需要导入 `forChild` 确保HTTP拦截器有效,一般可以直接在 SharedModule 直接使用 `forChild`
### 为什么只对开发环境有效?

Expand Down
5 changes: 1 addition & 4 deletions packages/mock/ng-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
"deleteDestPath": true,
"lib": {
"flatModuleFile": "mock",
"entryFile": "public_api.ts",
"umdModuleIds": {
"@delon/theme": "delon.theme"
}
"entryFile": "public_api.ts"
}
}
3 changes: 0 additions & 3 deletions packages/mock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,5 @@
"angular",
"component"
],
"peerDependencies": {
"@delon/theme": "PEER-0.0.0-PLACEHOLDER"
},
"sideEffects": false
}
4 changes: 4 additions & 0 deletions packages/mock/src/mock.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ export class DelonMockConfig {
force ?= false;
/** 是否打印 Mock 请求信息,弥补浏览器无Network信息 */
log ?= true;
/**
* 是否拦截命中后继续调用后续拦截器的 `intercept` 方法,默认:`true`
*/
executeOtherInterceptors ?= true;
}
72 changes: 32 additions & 40 deletions packages/mock/src/mock.interceptor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,15 @@
import {
HttpClient,
HttpHeaders,
HttpResponse,
HTTP_INTERCEPTORS,
} from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import {
Component,
Injector,
NgModule,
NgModuleFactoryLoader,
} from '@angular/core';
import { HttpClient, HttpEvent, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest, HttpResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { Component, NgModule, NgModuleFactoryLoader } from '@angular/core';
import { fakeAsync, inject, tick, TestBed, TestBedStatic } from '@angular/core/testing';
import { Router, RouterModule } from '@angular/router';
import {
RouterTestingModule,
SpyNgModuleFactoryLoader,
} from '@angular/router/testing';
import { _HttpClient, AlainThemeModule } from '@delon/theme';
import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing';

import * as Mock from 'mockjs';
import { Observable } from 'rxjs';
import { mapTo } from 'rxjs/operators';
import { MockRequest } from './interface';
import { DelonMockConfig } from './mock.config';
import { MockInterceptor } from './mock.interceptor';
import { DelonMockModule } from './mock.module';
import { MockService } from './mock.service';
import { MockStatusError } from './status.error';
Expand All @@ -50,13 +34,20 @@ const DATA = {
},
};

let otherRes = new HttpResponse();
class OtherInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req.clone()).pipe(mapTo(otherRes));
}
}

describe('mock: interceptor', () => {
let injector: TestBedStatic;
let srv: MockService = null;
let http: HttpClient;
let httpMock: HttpTestingController;

function genModule(options: DelonMockConfig, imports: any[] = [], spyConsole = true) {
function genModule(options: DelonMockConfig, imports: any[] = [], spyConsole = true, providers?: any[]) {
options = Object.assign(new DelonMockConfig(), options);
injector = TestBed.configureTestingModule({
declarations: [RootCmp],
Expand All @@ -70,9 +61,7 @@ describe('mock: interceptor', () => {
]),
DelonMockModule.forRoot(options),
].concat(imports),
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: MockInterceptor, multi: true },
],
providers: [].concat(providers || []),
});
srv = injector.get(MockService);
http = injector.get(HttpClient);
Expand All @@ -84,7 +73,7 @@ describe('mock: interceptor', () => {
}

describe('[default]', () => {
beforeEach(() => genModule({ data: DATA, delay: 1 }));
beforeEach(() => genModule({ executeOtherInterceptors: false, data: DATA, delay: 1 }));
it('should be init', (done: () => void) => {
http.get('/users').subscribe((res: any) => {
expect(res).not.toBeNull();
Expand Down Expand Up @@ -167,7 +156,6 @@ describe('mock: interceptor', () => {
done();
},
() => {
expect(console.log).toHaveBeenCalled();
expect(true).toBe(true);
done();
},
Expand Down Expand Up @@ -234,7 +222,7 @@ describe('mock: interceptor', () => {
selector: 'lazy',
template: '<router-outlet></router-outlet>',
})
class LayoutComponent {}
class LayoutComponent { }

@Component({
selector: 'child',
Expand All @@ -256,7 +244,7 @@ describe('mock: interceptor', () => {
]),
],
})
class LazyModule {}
class LazyModule { }

loader.stubbedModules = { expected: LazyModule };
const fixture = TestBed.createComponent(RootCmp);
Expand All @@ -270,15 +258,19 @@ describe('mock: interceptor', () => {
),
));
});
describe('[_HttpClient]', () => {
it('should be set to load status', (done: () => void) => {
genModule({ data: DATA, delay: 1 }, [AlainThemeModule.forRoot()], false);
const hc = injector.get(_HttpClient);
spyOn(hc, 'begin');
spyOn(hc, 'end');
hc.get('/users').subscribe(() => {
expect(hc.begin).toHaveBeenCalled();
expect(hc.end).toHaveBeenCalled();
describe('[executeOtherInterceptors]', () => {
beforeEach(() => {
genModule(
{ data: DATA, delay: 1, executeOtherInterceptors: true }, [], true, [
{ provide: HTTP_INTERCEPTORS, useClass: OtherInterceptor, multi: true },
]);
});

it('shoul working', (done) => {
otherRes = new HttpResponse({ body: { a: 1 } });
http.get('/users').subscribe((res: any) => {
expect(res).not.toBeNull();
expect(res.a).toBe(1);
done();
});
});
Expand All @@ -289,4 +281,4 @@ describe('mock: interceptor', () => {
selector: 'root-cmp',
template: `<router-outlet></router-outlet>`,
})
class RootCmp {}
class RootCmp { }
89 changes: 45 additions & 44 deletions packages/mock/src/mock.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
// tslint:disable:no-any
import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
import { HttpBackend, HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse, HttpResponseBase, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Injectable, Injector } from '@angular/core';
import { of, Observable, Observer } from 'rxjs';
import { delay, tap } from 'rxjs/operators';

import { _HttpClient } from '@delon/theme';
import { of, throwError, Observable } from 'rxjs';
import { delay } from 'rxjs/operators';

import { MockRequest } from './interface';
import { DelonMockConfig } from './mock.config';
import { MockService } from './mock.service';
import { MockStatusError } from './status.error';

class HttpMockInterceptorHandler implements HttpHandler {
constructor(private next: HttpHandler, private interceptor: HttpInterceptor) { }

handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
return this.interceptor.intercept(req, this.next);
}
}

@Injectable()
export class MockInterceptor implements HttpInterceptor {
constructor(private injector: Injector) { }
Expand All @@ -28,6 +27,7 @@ export class MockInterceptor implements HttpInterceptor {
delay: 300,
force: false,
log: true,
executeOtherInterceptors: true,
...this.injector.get(DelonMockConfig, null),
};
const rule = src.getRule(req.method, req.url.split('?')[0]);
Expand Down Expand Up @@ -72,50 +72,51 @@ export class MockInterceptor implements HttpInterceptor {
try {
res = rule.callback.call(this, mockRequest);
} catch (e) {
let errRes: HttpErrorResponse;
res = new HttpErrorResponse({
url: req.url,
headers: req.headers,
status: 400,
statusText: e.statusText || 'Unknown Error',
error: e.error,
});
if (e instanceof MockStatusError) {
errRes = new HttpErrorResponse({
url: req.url,
headers: req.headers,
status: e.status,
statusText: e.statusText || 'Unknown Error',
error: e.error,
});
if (config.log)
console.log(`%c💀${req.method}->${req.url}`, 'background:#000;color:#bada55', errRes, req);
} else {
console.log(`%c💀${req.method}->${req.url}`, 'background:#000;color:#bada55', `Please use MockStatusError to throw status error`, e, req);
res.status = e.status;
}
return new Observable((observer: Observer<HttpEvent<any>>) => {
observer.error(errRes);
});
}
break;
default:
res = rule.callback;
break;
}

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

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

const res$ = res instanceof HttpErrorResponse ? throwError(res) : of(res);

if (config.executeOtherInterceptors) {
const interceptors = this.injector.get(HTTP_INTERCEPTORS, []);
const lastInterceptors = interceptors.slice(interceptors.indexOf(this) + 1);
if (lastInterceptors.length > 0) {
const chain = lastInterceptors.reduceRight(
(_next, _interceptor) => new HttpMockInterceptorHandler(_next, _interceptor), {
handle: () => res$,
} as HttpBackend,
);
return chain.handle(req).pipe(delay(config.delay));
}
}
const hc = this.injector.get(_HttpClient, null);
if (hc) hc.begin();
return of(response).pipe(
delay(config.delay),
tap(() => {
if (hc) hc.end();
}),
);

return res$.pipe(delay(config.delay));
}
}

0 comments on commit d77e8e9

Please sign in to comment.