Skip to content

Commit

Permalink
feat: token to exclude all guards
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Oct 26, 2020
1 parent fce5b27 commit 7068784
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 13 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
38 changes: 30 additions & 8 deletions examples/TestRoutingGuard/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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({
Expand All @@ -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 {}

Expand All @@ -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(() => {
Expand Down
2 changes: 2 additions & 0 deletions lib/common/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const NG_MOCKS_TOUCHES = new InjectionToken<Set<any>>('NG_MOCKS_TOUCHES')
export const NG_MOCKS_OVERRIDES = new InjectionToken<Map<Type<any> | AbstractType<any>, MetadataOverride<any>>>(
'NG_MOCKS_OVERRIDES'
);
export const NG_GUARDS = new InjectionToken<void>('NG_MOCKS_GUARDS');
export const NG_INTERCEPTORS = new InjectionToken<void>('NG_MOCKS_INTERCEPTORS');

/**
* Can be changed any time.
Expand Down
47 changes: 42 additions & 5 deletions lib/mock-service/mock-service.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 7068784

Please sign in to comment.