From 7068784541c6a0ec1ca90b38cb4169ec373dd5e7 Mon Sep 17 00:00:00 2001 From: MG Date: Mon, 26 Oct 2020 17:18:50 +0100 Subject: [PATCH] feat: token to exclude all guards --- README.md | 7 ++++ examples/TestRoutingGuard/test.spec.ts | 38 ++++++++++++++++----- lib/common/lib.ts | 2 ++ lib/mock-service/mock-service.ts | 47 +++++++++++++++++++++++--- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3ecce75474..82d3af4799 100644 --- a/README.md +++ b/README.md @@ -1374,6 +1374,13 @@ beforeEach(() => .exclude(SomeDependency) .exclude(SomeInjectionToken) ); +// If we want to test guards we need to .keep guards we need, but +// what should we do with other guards we do not want to care about +// at all? The answer is to exclude `NG_GUARDS`, it will removal all +// the guards from routes except the explicitly configured ones. +beforeEach( + () => MockBuilder(MyGuard, MyModule).exclude(NG_GUARDS) // <- Token to remove all guards +); // If we want to replace something with something, // we should use .replace. diff --git a/examples/TestRoutingGuard/test.spec.ts b/examples/TestRoutingGuard/test.spec.ts index 10e25e2399..9868207e1b 100644 --- a/examples/TestRoutingGuard/test.spec.ts +++ b/examples/TestRoutingGuard/test.spec.ts @@ -3,7 +3,7 @@ import { Component, Injectable, NgModule, VERSION } from '@angular/core'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { CanActivate, Router, RouterModule, RouterOutlet } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; +import { MockBuilder, MockRender, NG_GUARDS, ngMocks } from 'ng-mocks'; import { from, Observable } from 'rxjs'; import { mapTo } from 'rxjs/operators'; @@ -34,6 +34,16 @@ class LoginGuard implements CanActivate { } } +// A side guard, when it has been mocked it blocks all routes, because `canActivate` returns undefined. +@Injectable() +class MockedGuard implements CanActivate { + protected readonly allow = true; + + canActivate(): boolean { + return this.allow; + } +} + // A simple component pretending a login form. // It will be mocked. @Component({ @@ -57,17 +67,26 @@ class DashboardComponent {} imports: [ RouterModule.forRoot([ { + canActivate: [MockedGuard, 'canActivateToken'], component: LoginComponent, path: 'login', }, { - canActivate: [LoginGuard], + canActivate: [LoginGuard, MockedGuard, 'canActivateToken'], component: DashboardComponent, path: '**', }, ]), ], - providers: [LoginService, LoginGuard], + providers: [ + LoginService, + LoginGuard, + MockedGuard, + { + provide: 'canActivateToken', + useValue: () => true, + }, + ], }) class TargetModule {} @@ -76,11 +95,14 @@ describe('TestRoutingGuard', () => { // test its integration with RouterModule. Therefore, we pass // the guard as the first parameter of MockBuilder. Then, to // correctly satisfy its initialization, we need to pass its module - // as the second parameter. And, the last but not the least, we - // need to avoid mocking of RouterModule to have its routes, and to - // add RouterTestingModule.withRoutes([]), yes yes, with empty - // routes to have tools for testing. - beforeEach(() => MockBuilder(LoginGuard, TargetModule).keep(RouterModule).keep(RouterTestingModule.withRoutes([]))); + // as the second parameter. The next step is to avoid mocking of + // RouterModule to have its routes, and to add + // RouterTestingModule.withRoutes([]), yes yes, with empty routes + // to have tools for testing. And the last thing is to exclude + // `NG_GUARDS` to remove all other guards. + beforeEach(() => + MockBuilder(LoginGuard, TargetModule).exclude(NG_GUARDS).keep(RouterModule).keep(RouterTestingModule.withRoutes([])) + ); // It is important to run routing tests in fakeAsync. it('redirects to login', fakeAsync(() => { diff --git a/lib/common/lib.ts b/lib/common/lib.ts index d5d3ae059a..db361ec929 100644 --- a/lib/common/lib.ts +++ b/lib/common/lib.ts @@ -36,6 +36,8 @@ export const NG_MOCKS_TOUCHES = new InjectionToken>('NG_MOCKS_TOUCHES') export const NG_MOCKS_OVERRIDES = new InjectionToken | AbstractType, MetadataOverride>>( 'NG_MOCKS_OVERRIDES' ); +export const NG_GUARDS = new InjectionToken('NG_MOCKS_GUARDS'); +export const NG_INTERCEPTORS = new InjectionToken('NG_MOCKS_INTERCEPTORS'); /** * Can be changed any time. diff --git a/lib/mock-service/mock-service.ts b/lib/mock-service/mock-service.ts index e1057bb68b..f7db3e8d06 100644 --- a/lib/mock-service/mock-service.ts +++ b/lib/mock-service/mock-service.ts @@ -1,6 +1,6 @@ import { Injector, Provider } from '@angular/core'; -import { isNgInjectionToken } from '../common'; +import { isNgInjectionToken, NG_GUARDS } from '../common'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; import { MockProvider } from '../mock-module'; @@ -223,24 +223,61 @@ const mockServiceHelperPrototype = { if (ngMocksUniverse.cacheMocks.has(value)) { return ngMocksUniverse.cacheMocks.get(value); } - if (typeof value !== 'object') { + if (typeof value !== 'object' || !value) { return value; } + let mocked: any; let updated = false; + if (Array.isArray(value)) { mocked = []; - for (let key = 0; key < value.length; key += 1) { - mocked[key] = mockServiceHelper.replaceWithMocks(value[key]); - updated = updated || mocked[key] !== value[key]; + for (const valueItem of value) { + if (ngMocksUniverse.builder.has(valueItem) && ngMocksUniverse.builder.get(valueItem) === null) { + updated = updated || true; + continue; + } + mocked.push(mockServiceHelper.replaceWithMocks(valueItem)); + updated = updated || mocked[mocked.length - 1] !== valueItem; } } else if (value) { mocked = {}; for (const key of Object.keys(value)) { + if (ngMocksUniverse.builder.has(value[key]) && ngMocksUniverse.builder.get(value[key]) === null) { + updated = updated || true; + continue; + } mocked[key] = mockServiceHelper.replaceWithMocks(value[key]); updated = updated || mocked[key] !== value[key]; } + + // Removal of guards. + for (const section of ['canActivate', 'canActivateChild', 'canDeactivate', 'canLoad']) { + if (!Array.isArray(mocked[section])) { + continue; + } + + const guards: any[] = []; + for (const guard of mocked[section]) { + if (ngMocksUniverse.builder.has(guard) && ngMocksUniverse.builder.get(guard) !== null) { + guards.push(guard); + continue; + } + if (ngMocksUniverse.builder.has(NG_GUARDS) && ngMocksUniverse.builder.get(NG_GUARDS) === null) { + continue; + } + guards.push(guard); + } + if (mocked[section].length !== guards.length) { + updated = updated || true; + mocked = { + ...mocked, + [section]: guards, + }; + } + } } + if (updated) { Object.setPrototypeOf(mocked, Object.getPrototypeOf(value)); return mocked;