diff --git a/.circleci/config.yml b/.circleci/config.yml index a418af736c..05dacda544 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -378,6 +378,8 @@ jobs: rm $P/examples/TestRoutingResolver/test.spec.ts rm $P/tests/issue-4282/test.spec.ts rm $P/tests/issue-4282/global.spec.ts + rm $P/examples/TestRoutingGuard/can-*.spec.ts + rm $P/examples/TestRoutingResolver/fn.spec.ts - run: name: Unit Tests command: | diff --git a/docs/articles/api/MockBuilder.md b/docs/articles/api/MockBuilder.md index 11193176a8..5b02f56f3f 100644 --- a/docs/articles/api/MockBuilder.md +++ b/docs/articles/api/MockBuilder.md @@ -476,13 +476,37 @@ even if it has been imported or declared in nested modules. ### `NG_MOCKS_GUARDS` token -If we want to test guards, we need to [`.keep`](#keep) them, but what should we do with other guards we do not want to care about at all? -The answer is to exclude `NG_MOCKS_GUARDS` token, it will **remove all the guards** from routes except the explicitly configured ones. +`NG_MOCKS_GUARDS` helps to **remove guards from all routes** in a test. +It's useful if you want to test a specific guard. +To do so, you need to [`.exclude`](#exclude) `NG_MOCKS_GUARDS` and to [`.keep`](#keep) the guard. ```ts beforeEach(() => { - return MockBuilder(MyGuard, MyModule) - .exclude(NG_MOCKS_GUARDS); + return MockBuilder( + [RouterModule, RouterTestingModule.withRoutes([])], + ModuleWithRoutes, + ) + .exclude(NG_MOCKS_GUARDS) // <- remotes all guards + .keep(GuardUnderTest) // <- but keeps GuardUnderTest + ; +}); +``` + +### `NG_MOCKS_RESOLVERS` token + +`NG_MOCKS_RESOLVERS` helps to **remove all resolves from all routes** in a test. +It's useful if you want to test a specific resolver. +To do so, you need to [`.exclude`](#exclude) `NG_MOCKS_RESOLVERS` and to [`.keep`](#keep) the resolver. + +```ts +beforeEach(() => { + return MockBuilder( + [RouterModule, RouterTestingModule.withRoutes([])], + ModuleWithRoutes, + ) + .exclude(NG_MOCKS_RESOLVERS) // <- remotes all resolvers + .keep(ResolverUnderTest) // <- but keeps ResolverUnderTest + ; }); ``` diff --git a/docs/articles/guides/routing-guard.md b/docs/articles/guides/routing-guard.md index 36b49b8d5e..11116afa5c 100644 --- a/docs/articles/guides/routing-guard.md +++ b/docs/articles/guides/routing-guard.md @@ -8,63 +8,171 @@ If you have not read ["How to test a route"](route.md), please do it first. To test a guard means that we need to mock everything except the guard and `RouterModule`. But, what if we have several guards? If we mocked them they would block routes due to falsy returns of their mocked methods. -**To skip guards in angular tests `ng-mocks` provides `NG_MOCKS_GUARDS` token**, we should pass it into `.exclude`, then all other guards will be -excluded from `TestBed` and we can be sure, that we are **testing only the guard we want**. +**To remove guards in angular tests `ng-mocks` provides `NG_MOCKS_GUARDS` token**, we should pass it into `.exclude`, then all other guards will be +excluded from `TestBed`, and we can be sure that we are **testing only the guard we want**. + +The example below is applicable for all types of guards: + +- `canActivate` - + [CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/can-activate.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanActivate), + [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-activate.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanActivate) +- `canActivateChild` - + [CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/can-activateChild.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanActivateChild), + [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-activateChild.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanActivateChild) +- `canDeactivate` - + [CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/can-deactivate.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanDeactivate), + [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-deactivate.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanDeactivate) +- `canMatch` - + [CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch), + [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch) +- `canLoad` - + [CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch), + [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch) +- class guards (legacy) - + [CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/test.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3Atest), + [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/test.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3Atest) + +## Functional Guards + +A functional guard is a simple function, and not a service or a token how it was before Angular 14. +A guard resides in the configuration of routes, +which is defined as an import of `RouterModule.forRoot` or `RouterModule.forChild` in a module. + +To test a guard, you need the guard and the module which defines a route with the guard. +For simplicity, let's call the guard `loginGuard`, and the module `TargetModule`. + +The guard should be tested in isolation, to avoid side effects of other guards. +Also, `RouterModule` and its dependencies should be provided in a test +to ensure that the guard has been connected to its route correctly and you can assert `Location` and/or `Router`. +The rest can be mocks. ```ts beforeEach(() => MockBuilder( - // Things to keep and export. + // first parameter + // providing RouterModule and its dependencies [ - LoginGuard, RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS, - ], - // Things to mock + ], + + // second parameter + // Mocking definition of TargetModule TargetModule, - ).exclude(NG_MOCKS_GUARDS) + ) + + // chain + // excluding all guards to avoid side effects + .exclude(NG_MOCKS_GUARDS) + + // chain + // keeping loginGuard for testing + .keep(loginGuard) ); ``` -Let's assume that we have `LoginGuard` that redirects all routes to `/login` if a user is not logged in. -It means when we initialize the router we should end up on `/login`. So let's do that. +Let's assume that the guard redirects all routes to `/login` if a user is not logged in. +It means when the app has been initialized, the router should end up on `/login`. + +Let's assert that: + +1. render a router outlet +1. initialize navigation +1. assert the location + +To render a router outlet, you can use `MockRender` with empty parameters. + +```ts +const fixture = MockRender(RouterOutlet, {}); +``` + +Now, you can get `Router` and `Location`. +The first one is needed for the initialization, +the second one for assertion. + +```ts +const router = ngMocks.get(Router); +const location = ngMocks.get(Location); +``` + +To initialize navigation, you need to call `router.initialNavigation`, +and then `tick` to ensure that the route has been initialized and rendered. ```ts if (fixture.ngZone) { fixture.ngZone.run(() => router.initialNavigation()); - tick(); + tick(); // is needed for rendering of the current route. } ``` -Now we can assert the current state. +Now, the location can be asserted. ```ts expect(location.path()).toEqual('/login'); -expect(() => ngMocks.find(fixture, LoginComponent)).not.toThrow(); ``` + +Profit, [an example of a test for a functional guard](#live-example). + +## Class Guards (legacy) + +If your code has guards which a classes and angular services, +the process is exactly the same as for [functional guards](#functional-guards). + +For example, if the class of the guard is called `LoginGuard`, +the configuration of `TestBed` should be the next: + +```ts +beforeEach(() => + MockBuilder( + // first parameter + // providing RouterModule and its dependencies + [ + RouterModule, + RouterTestingModule.withRoutes([]), + NG_MOCKS_ROOT_PROVIDERS, + ], + + // second parameter + // Mocking definition of TargetModule + TargetModule, + ) + + // chain + // excluding all guards to avoid side effects + .exclude(NG_MOCKS_GUARDS) + + // chain + // keeping LoginGuard for testing + .keep(LoginGuard) +); +``` + +Profit. + ## Live example -- [Try it on CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/test.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard) -- [Try it on StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/test.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard) +- [Try it on CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingGuard/can-activate.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanActivate) +- [Try it on StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-activate.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanActivate) -```ts title="https://github.com/help-me-mom/ng-mocks/blob/master/examples/TestRoutingGuard/test.spec.ts" +```ts title="https://github.com/help-me-mom/ng-mocks/blob/master/examples/TestRoutingGuard/can-activate.spec.ts" import { Location } from '@angular/common'; import { Component, + inject, Injectable, NgModule, } from '@angular/core'; import { fakeAsync, tick } from '@angular/core/testing'; import { - CanActivate, + CanActivateFn, Router, RouterModule, RouterOutlet, } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { from, Observable } from 'rxjs'; +import { from } from 'rxjs'; import { mapTo } from 'rxjs/operators'; import { @@ -83,32 +191,17 @@ class LoginService { } // A guard we want to test. -@Injectable() -class LoginGuard implements CanActivate { - public constructor( - protected router: Router, - protected service: LoginService, - ) {} - - public canActivate(): boolean | Observable { - if (this.service.isLoggedIn) { - return true; - } - - return from(this.router.navigate(['/login'])).pipe(mapTo(false)); +const canActivateGuard: CanActivateFn = (route, state) => { + if (route && state && inject(LoginService).isLoggedIn) { + return true; } -} -// A side guard, when it has been replaced with its mock copy -// it blocks all routes, because `canActivate` returns undefined. -@Injectable() -class MockGuard implements CanActivate { - protected readonly allow = true; + return from(inject(Router).navigate(['/login'])).pipe(mapTo(false)); +}; - public canActivate(): boolean { - return this.allow; - } -} +// Another guard like in a real world example. +// The guard should be removed from testing to avoid side effects on the route. +const sideEffectGuard: CanActivateFn = () => false; // A simple component pretending a login form. // It will be replaced with a mock copy. @@ -116,7 +209,9 @@ class MockGuard implements CanActivate { selector: 'login', template: 'login', }) -class LoginComponent {} +class LoginComponent { + public loginTestRoutingGuardCanActivate() {} +} // A simple component pretending a protected dashboard. // It will be replaced with a mock copy. @@ -124,7 +219,9 @@ class LoginComponent {} selector: 'dashboard', template: 'dashboard', }) -class DashboardComponent {} +class DashboardComponent { + public dashboardTestRoutingGuardCanActivate() {} +} // Definition of the routing module. @NgModule({ @@ -133,56 +230,50 @@ class DashboardComponent {} imports: [ RouterModule.forRoot([ { - canActivate: [MockGuard, 'canActivateToken'], component: LoginComponent, path: 'login', }, { - canActivate: [LoginGuard, MockGuard, 'canActivateToken'], + canActivate: [canActivateGuard, sideEffectGuard], component: DashboardComponent, path: '**', }, ]), ], - providers: [ - LoginService, - LoginGuard, - MockGuard, - { - provide: 'canActivateToken', - useValue: () => true, - }, - ], + providers: [LoginService], }) class TargetModule {} -describe('TestRoutingGuard', () => { - // Because we want to test the guard, it means that we want to - // 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. 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_MOCKS_GUARDS` to remove all other guards. +describe('TestRoutingGuard:canActivate', () => { + // Because we want to test a canActive guard, it means that we want to + // test its integration with RouterModule. + // Therefore, RouterModule and the guard should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (yes, with empty routes). + // The module with routes and the guard should be specified + // as the second parameter of MockBuilder. + // Then `NG_MOCKS_GUARDS` should be excluded to remove all guards, + // and `canActivateGuard` should be kept to let you test it. beforeEach(() => { return MockBuilder( [ - LoginGuard, RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS, ], TargetModule, - ).exclude(NG_MOCKS_GUARDS); + ) + .exclude(NG_MOCKS_GUARDS) + .keep(canActivateGuard); }); // It is important to run routing tests in fakeAsync. it('redirects to login', fakeAsync(() => { const fixture = MockRender(RouterOutlet, {}); - const router: Router = fixture.point.injector.get(Router); - const location: Location = fixture.point.injector.get(Location); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); // First we need to initialize navigation. if (fixture.ngZone) { @@ -198,10 +289,9 @@ describe('TestRoutingGuard', () => { it('loads dashboard', fakeAsync(() => { const fixture = MockRender(RouterOutlet, {}); - const router: Router = fixture.point.injector.get(Router); - const location: Location = fixture.point.injector.get(Location); - const loginService: LoginService = - fixture.point.injector.get(LoginService); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + const loginService = ngMocks.get(LoginService); // Letting the guard know we have been logged in. loginService.isLoggedIn = true; diff --git a/docs/articles/guides/routing-resolver.md b/docs/articles/guides/routing-resolver.md index 2ed5fa7baa..d2ca712eb5 100644 --- a/docs/articles/guides/routing-resolver.md +++ b/docs/articles/guides/routing-resolver.md @@ -6,22 +6,47 @@ sidebar_label: Routing resolver If you did not read ["How to test a route"](route.md), please do it first. -When we want to test a resolver it means we need to mock everything except the resolver and `RouterModule`. -Optionally, we can disable guards to avoid influence of their mocked methods returning falsy values and blocking routes. +When you want to test a resolver, you need to remove all other resolves and guards to avoid side effects, +to mock declarations to test the resolver in isolation, +and to keep `RouterModule` and its dependencies to assert results on `Location` and `ActivatedRoute`. + +## Functional resolvers + +A functional resolver is a simple function which uses `inject` to get another services and to fetch data for its route. +It's important to note that a functional resolver isn't defined as a service or a token, +and, therefore, it exists only in the definition of a route. + +Let's assume, the resolver is called `dataResolver` and the module with its route `TargetModule`. + +To configure `TestBed` as described above, the code can be next: ```ts beforeEach(() => MockBuilder( - // Things to keep and export. + // first parameter + // providing RouterModule and its dependencies [ - DataResolver, RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS, ], - // Things to mock. + + // second parameter + // Mocking definition of TargetModule TargetModule, - ).exclude(NG_MOCKS_GUARDS) + ) + + // chain + // excluding all guards to avoid side effects + .exclude(NG_MOCKS_GUARDS) + + // chain + // excluding all resolvers to avoid side effects + .exclude(NG_MOCKS_RESOLVERS) + + // chain + // keeping dataResolver for testing + .keep(dataResolver) ); ``` @@ -34,16 +59,15 @@ const fixture = MockRender(RouterOutlet, {}); // {} is required to leave inputs Additionally, we also need to properly customize mocked services if the resolver is using them to fetch data. ```ts -const dataService = TestBed.get(DataService); - +const dataService = ngMocks.get(DataService); dataService.data = () => from([false]); ``` The next step is to go to the route where the resolver is, and to trigger initialization of the router. ```ts -const location = TestBed.get(Location); -const router = TestBed.get(Router); +const location = ngMocks.get(Location); +const router = ngMocks.get(Router); location.go('/target'); if (fixture.ngZone) { @@ -58,7 +82,7 @@ Let's pretend that `/target` renders `TargetComponent`. ```ts const el = ngMocks.find(fixture, TargetComponent); -const route: ActivatedRoute = el.injector.get(ActivatedRoute); +const route = ngMocks.get(el, ActivatedRoute); ``` Profit, now we can assert the data the resolver has provided. @@ -71,18 +95,63 @@ expect(route.snapshot.data).toEqual({ }); ``` +## Class Resolver (legacy) + +If your code has resolvers which a classes and angular services, +the process is exactly the same as for [functional resolvers](#functional-resolvers). + +For example, if the class of the resolver is called `DataResolver`, +the configuration of `TestBed` should be the next: + +```ts +beforeEach(() => + MockBuilder( + // first parameter + // providing RouterModule and its dependencies + [ + RouterModule, + RouterTestingModule.withRoutes([]), + NG_MOCKS_ROOT_PROVIDERS, + ], + + // second parameter + // Mocking definition of TargetModule + TargetModule, + ) + + // chain + // excluding all guards to avoid side effects + .exclude(NG_MOCKS_GUARDS) + + // chain + // excluding all resolvers to avoid side effects + .exclude(NG_MOCKS_RESOLVERS) + + // chain + // keeping DataResolver for testing + .keep(DataResolver) +); +``` + +Profit. + ## Live example -- [Try it on CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingResolver/test.spec.ts&initialpath=%3Fspec%3DTestRoutingResolver) -- [Try it on StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingResolver/test.spec.ts&initialpath=%3Fspec%3DTestRoutingResolver) +- [Try it on CodeSandbox](https://codesandbox.io/s/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=/src/examples/TestRoutingResolver/fn.spec.ts&initialpath=%3Fspec%3DTestRoutingResolver%3Afn) +- [Try it on StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingResolver/fn.spec.ts&initialpath=%3Fspec%3DTestRoutingResolver%3Afn) -```ts title="https://github.com/help-me-mom/ng-mocks/blob/master/examples/TestRoutingResolver/test.spec.ts" -import { Location } from '@angular/common'; -import { Component, Injectable, NgModule } from '@angular/core'; +```ts title="https://github.com/help-me-mom/ng-mocks/blob/master/examples/TestRoutingResolver/fn.spec.ts" +import { Location } from 'import { Location } from '@angular/common'; +import { + Component, + inject, + Injectable, + NgModule, +} from '@angular/core'; import { fakeAsync, tick } from '@angular/core/testing'; import { ActivatedRoute, - Resolve, + ResolveFn, Router, RouterModule, RouterOutlet, @@ -94,6 +163,8 @@ import { map } from 'rxjs/operators'; import { MockBuilder, MockRender, + NG_MOCKS_GUARDS, + NG_MOCKS_RESOLVERS, NG_MOCKS_ROOT_PROVIDERS, ngMocks, } from 'ng-mocks'; @@ -109,26 +180,15 @@ class DataService { } // A resolver we want to test. -@Injectable() -class DataResolver implements Resolve<{ flag: boolean }> { - public constructor(protected service: DataService) {} - - public resolve() { - return combineLatest([this.service.data()]).pipe( - map(([flag]) => ({ flag })), - ); - } -} +const dataResolver: ResolveFn> = () => + combineLatest([inject(DataService).data()]).pipe( + map(([flag]) => ({ flag })), + ); // A resolver we want to ignore. -@Injectable() -class MockResolver implements Resolve<{ mock: boolean }> { - protected mock = true; - - public resolve() { - return of({ mock: this.mock }); - } -} +const sideEffectResolver: ResolveFn< + Observable<{ mock: boolean }> +> = () => of({ mock: true }); // A dummy component. // It will be replaced with a mock copy. @@ -136,7 +196,9 @@ class MockResolver implements Resolve<{ mock: boolean }> { selector: 'route', template: 'route', }) -class RouteComponent {} +class RouteComponent { + public routeTestRoutingFnResolver() {} +} // Definition of the routing module. @NgModule({ @@ -148,44 +210,48 @@ class RouteComponent {} component: RouteComponent, path: 'route', resolve: { - data: DataResolver, - mock: MockResolver, + data: dataResolver, + mock: sideEffectResolver, }, }, ]), ], - providers: [DataService, DataResolver, MockResolver], + providers: [DataService], }) class TargetModule {} -describe('TestRoutingResolver', () => { - // Because we want to test the resolver, it means that we want to - // test its integration with RouterModule. Therefore, we pass - // the resolver 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 keep RouterModule to have its routes, and to - // add RouterTestingModule.withRoutes([]), yes yes, with empty - // routes to have tools for testing. +describe('TestRoutingResolver:fn', () => { + // Because we want to test a resolver, it means that we want to + // test its integration with RouterModule. + // Therefore, RouterModule and the resolver should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (yes, with empty routes). + // The module with routes and the resolver should be specified + // as the second parameter of MockBuilder. + // Then `NG_MOCKS_RESOLVERS` should be excluded to remove all resolvers, + // and `dataResolver` should be kept to let you test it. beforeEach(() => { return MockBuilder( [ - DataResolver, RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS, ], TargetModule, - ); + ) + .exclude(NG_MOCKS_GUARDS) + .exclude(NG_MOCKS_RESOLVERS) + .keep(dataResolver); }); // It is important to run routing tests in fakeAsync. it('provides data to on the route', fakeAsync(() => { const fixture = MockRender(RouterOutlet, {}); - const router: Router = fixture.point.injector.get(Router); - const location: Location = fixture.point.injector.get(Location); - const dataService: DataService = - fixture.point.injector.get(DataService); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + const dataService = ngMocks.get(DataService); // DataService has been replaced with a mock copy, // let's set a custom value we will assert later on. @@ -205,16 +271,14 @@ describe('TestRoutingResolver', () => { // Let's extract ActivatedRoute of the current component. const el = ngMocks.find(RouteComponent); - const route: ActivatedRoute = el.injector.get(ActivatedRoute); + const route = ngMocks.findInstance(el, ActivatedRoute); // Now we can assert that it has expected data. - expect(route.snapshot.data).toEqual( - jasmine.objectContaining({ - data: { - flag: false, - }, - }), - ); + expect(route.snapshot.data).toEqual({ + data: { + flag: false, + }, + }); })); }); ``` diff --git a/examples/TestRoutingGuard/can-activate-child.spec.ts b/examples/TestRoutingGuard/can-activate-child.spec.ts new file mode 100644 index 0000000000..391d8aa027 --- /dev/null +++ b/examples/TestRoutingGuard/can-activate-child.spec.ts @@ -0,0 +1,163 @@ +import { Location } from '@angular/common'; +import { + Component, + inject, + Injectable, + NgModule, + VERSION, +} from '@angular/core'; +import { fakeAsync, tick } from '@angular/core/testing'; +import { + CanActivateChildFn, + Router, + RouterModule, + RouterOutlet, +} from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { from } from 'rxjs'; +import { mapTo } from 'rxjs/operators'; + +import { + MockBuilder, + MockRender, + NG_MOCKS_GUARDS, + NG_MOCKS_ROOT_PROVIDERS, + ngMocks, +} from 'ng-mocks'; + +// A simple service simulating login check. +// It will be replaced with its mock copy. +@Injectable() +class LoginService { + public isLoggedIn = false; +} + +// A guard we want to test. +const canActivateChildGuard: CanActivateChildFn = (route, state) => { + if (route && state && inject(LoginService).isLoggedIn) { + return true; + } + + return from(inject(Router).navigate(['/login'])).pipe(mapTo(false)); +}; + +// Another guard like in a real world example. +// The guard should be removed from testing to avoid side effects on the route. +const sideEffectGuard: CanActivateChildFn = () => false; + +// A simple component pretending a login form. +// It will be replaced with a mock copy. +@Component({ + selector: 'login', + template: 'login', +}) +class LoginComponent { + public loginTestRoutingGuardCanActivateChild() {} +} + +// A simple component pretending a protected dashboard. +// It will be replaced with a mock copy. +@Component({ + selector: 'dashboard', + template: 'dashboard', +}) +class DashboardComponent { + public dashboardTestRoutingGuardCanActivateChild() {} +} + +// Definition of the routing module. +@NgModule({ + declarations: [LoginComponent, DashboardComponent], + exports: [RouterModule], + imports: [ + RouterModule.forRoot([ + { + component: LoginComponent, + path: 'login', + }, + { + canActivateChild: [canActivateChildGuard, sideEffectGuard], + path: '', + children: [ + { + component: DashboardComponent, + path: '**', + }, + ], + }, + ]), + ], + providers: [LoginService], +}) +class TargetModule {} + +describe('TestRoutingGuard:canActivateChild', () => { + // Because we want to test a canActivateChild guard, it means that we want to + // test its integration with RouterModule. + // Therefore, RouterModule and the guard should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (yes, with empty routes). + // The module with routes and the guard should be specified + // as the second parameter of MockBuilder. + // Then `NG_MOCKS_GUARDS` should be excluded to remove all guards, + // and `canActivateGuard` should be kept to let you test it. + beforeEach(() => { + return MockBuilder( + [ + RouterModule, + RouterTestingModule.withRoutes([]), + NG_MOCKS_ROOT_PROVIDERS, + ], + TargetModule, + ) + .exclude(NG_MOCKS_GUARDS) + .keep(canActivateChildGuard); + }); + + // It is important to run routing tests in fakeAsync. + it('redirects to login', fakeAsync(() => { + if (Number.parseInt(VERSION.major, 10) < 7) { + pending('Need Angular 7+'); + + return; + } + + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // Because by default we are not logged, the guard should + // redirect us /login page. + expect(location.path()).toEqual('/login'); + expect(() => ngMocks.find(LoginComponent)).not.toThrow(); + })); + + it('loads dashboard', fakeAsync(() => { + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + const loginService = ngMocks.get(LoginService); + + // Letting the guard know we have been logged in. + loginService.isLoggedIn = true; + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // Because now we are logged in, the guard should let us land on + // the dashboard. + expect(location.path()).toEqual('/'); + expect(() => ngMocks.find(DashboardComponent)).not.toThrow(); + })); +}); diff --git a/examples/TestRoutingGuard/can-activate.spec.ts b/examples/TestRoutingGuard/can-activate.spec.ts new file mode 100644 index 0000000000..20c1b7d906 --- /dev/null +++ b/examples/TestRoutingGuard/can-activate.spec.ts @@ -0,0 +1,158 @@ +import { Location } from '@angular/common'; +import { + Component, + inject, + Injectable, + NgModule, + VERSION, +} from '@angular/core'; +import { fakeAsync, tick } from '@angular/core/testing'; +import { + CanActivateFn, + Router, + RouterModule, + RouterOutlet, +} from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { from } from 'rxjs'; +import { mapTo } from 'rxjs/operators'; + +import { + MockBuilder, + MockRender, + NG_MOCKS_GUARDS, + NG_MOCKS_ROOT_PROVIDERS, + ngMocks, +} from 'ng-mocks'; + +// A simple service simulating login check. +// It will be replaced with its mock copy. +@Injectable() +class LoginService { + public isLoggedIn = false; +} + +// A guard we want to test. +const canActivateGuard: CanActivateFn = (route, state) => { + if (route && state && inject(LoginService).isLoggedIn) { + return true; + } + + return from(inject(Router).navigate(['/login'])).pipe(mapTo(false)); +}; + +// Another guard like in a real world example. +// The guard should be removed from testing to avoid side effects on the route. +const sideEffectGuard: CanActivateFn = () => false; + +// A simple component pretending a login form. +// It will be replaced with a mock copy. +@Component({ + selector: 'login', + template: 'login', +}) +class LoginComponent { + public loginTestRoutingGuardCanActivate() {} +} + +// A simple component pretending a protected dashboard. +// It will be replaced with a mock copy. +@Component({ + selector: 'dashboard', + template: 'dashboard', +}) +class DashboardComponent { + public dashboardTestRoutingGuardCanActivate() {} +} + +// Definition of the routing module. +@NgModule({ + declarations: [LoginComponent, DashboardComponent], + exports: [RouterModule], + imports: [ + RouterModule.forRoot([ + { + component: LoginComponent, + path: 'login', + }, + { + canActivate: [canActivateGuard, sideEffectGuard], + component: DashboardComponent, + path: '**', + }, + ]), + ], + providers: [LoginService], +}) +class TargetModule {} + +describe('TestRoutingGuard:canActivate', () => { + // Because we want to test a canActive guard, it means that we want to + // test its integration with RouterModule. + // Therefore, RouterModule and the guard should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (yes, with empty routes). + // The module with routes and the guard should be specified + // as the second parameter of MockBuilder. + // Then `NG_MOCKS_GUARDS` should be excluded to remove all guards, + // and `canActivateGuard` should be kept to let you test it. + beforeEach(() => { + return MockBuilder( + [ + RouterModule, + RouterTestingModule.withRoutes([]), + NG_MOCKS_ROOT_PROVIDERS, + ], + TargetModule, + ) + .exclude(NG_MOCKS_GUARDS) + .keep(canActivateGuard); + }); + + // It is important to run routing tests in fakeAsync. + it('redirects to login', fakeAsync(() => { + if (Number.parseInt(VERSION.major, 10) < 7) { + pending('Need Angular 7+'); + + return; + } + + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // Because by default we are not logged, the guard should + // redirect us /login page. + expect(location.path()).toEqual('/login'); + expect(() => ngMocks.find(LoginComponent)).not.toThrow(); + })); + + it('loads dashboard', fakeAsync(() => { + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + const loginService = ngMocks.get(LoginService); + + // Letting the guard know we have been logged in. + loginService.isLoggedIn = true; + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // Because now we are logged in, the guard should let us land on + // the dashboard. + expect(location.path()).toEqual('/'); + expect(() => ngMocks.find(DashboardComponent)).not.toThrow(); + })); +}); diff --git a/examples/TestRoutingGuard/can-deactivate.spec.ts b/examples/TestRoutingGuard/can-deactivate.spec.ts new file mode 100644 index 0000000000..f6dd62970c --- /dev/null +++ b/examples/TestRoutingGuard/can-deactivate.spec.ts @@ -0,0 +1,176 @@ +import { Location } from '@angular/common'; +import { + Component, + inject, + Injectable, + NgModule, + VERSION, +} from '@angular/core'; +import { fakeAsync, tick } from '@angular/core/testing'; +import { + CanDeactivateFn, + Router, + RouterModule, + RouterOutlet, +} from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { + MockBuilder, + MockRender, + NG_MOCKS_GUARDS, + NG_MOCKS_ROOT_PROVIDERS, + ngMocks, +} from 'ng-mocks'; + +// A simple service simulating login check. +// It will be replaced with its mock copy. +@Injectable() +class LoginService { + public isLoggedIn = false; +} + +// A guard we want to test. +const canDeactivateGuard: CanDeactivateFn = ( + route, + state, +) => { + return ( + (route && state && inject(LoginService).isLoggedIn) || + state.url.length === 0 || + state.url[0].path !== 'login' + ); +}; + +// Another guard like in a real world example, +// which should be removed from testing to avoid side effects on the route. +const sideEffectGuard: CanDeactivateFn = () => false; + +// A simple component pretending a login form. +// It will be replaced with a mock copy. +@Component({ + selector: 'login', + template: 'login', +}) +class LoginComponent { + public loginTestRoutingGuardCanDeactivate() {} +} + +// A simple component pretending a protected dashboard. +// It will be replaced with a mock copy. +@Component({ + selector: 'dashboard', + template: 'dashboard', +}) +class DashboardComponent { + public dashboardTestRoutingGuardCanDeactivate() {} +} + +// Definition of the routing module. +@NgModule({ + declarations: [LoginComponent, DashboardComponent], + exports: [RouterModule], + imports: [ + RouterModule.forRoot([ + { + component: DashboardComponent, + path: 'dashboard', + }, + { + canDeactivate: [canDeactivateGuard, sideEffectGuard], + component: LoginComponent, + path: '**', + }, + ]), + ], + providers: [LoginService], +}) +class TargetModule {} + +describe('TestRoutingGuard:canDeactivate', () => { + // Because we want to test a canDeactive guard, it means that we want to + // test its integration with RouterModule. + // Therefore, RouterModule and the guard should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (yes, with empty routes). + // The module with routes and the guard should be specified + // as the second parameter of MockBuilder. + // Then `NG_MOCKS_GUARDS` should be excluded to remove all guards, + // and `canActivateGuard` should be kept to let you test it. + beforeEach(() => { + return MockBuilder( + [ + RouterModule, + RouterTestingModule.withRoutes([]), + NG_MOCKS_ROOT_PROVIDERS, + ], + TargetModule, + ) + .exclude(NG_MOCKS_GUARDS) + .keep(canDeactivateGuard); + }); + + // It is important to run routing tests in fakeAsync. + it('cannot leave login', fakeAsync(() => { + if (Number.parseInt(VERSION.major, 10) < 7) { + pending('Need Angular 7+'); + + return; + } + + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + router.navigate(['/login']); + tick(); + + // We should be at /login page, which doesn't allow deactivation. + expect(location.path()).toEqual('/login'); + + // Trying to leave /login page. + router.navigate(['/dashboard']); + tick(); + + // We are still at /login page due to the guard. + expect(location.path()).toEqual('/login'); + expect(() => ngMocks.find(LoginComponent)).not.toThrow(); + })); + + it('can leave login', fakeAsync(() => { + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + const loginService = ngMocks.get(LoginService); + + // Letting the guard know we have been logged in. + loginService.isLoggedIn = true; + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // We should be at /login page. + router.navigate(['/login']); + tick(); + expect(location.path()).toEqual('/login'); + + // Trying to leave /login page. + router.navigate(['/dashboard']); + tick(); + + // Because now we are logged in, the guard lets us land on + // the /dashboard page. + expect(location.path()).toEqual('/dashboard'); + expect(() => ngMocks.find(DashboardComponent)).not.toThrow(); + })); +}); diff --git a/examples/TestRoutingGuard/can-match.spec.ts b/examples/TestRoutingGuard/can-match.spec.ts new file mode 100644 index 0000000000..6086deac7a --- /dev/null +++ b/examples/TestRoutingGuard/can-match.spec.ts @@ -0,0 +1,163 @@ +import { Location } from '@angular/common'; +import { + Component, + inject, + Injectable, + NgModule, + VERSION, +} from '@angular/core'; +import { fakeAsync, tick } from '@angular/core/testing'; +import { + CanMatchFn, + Router, + RouterModule, + RouterOutlet, +} from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { from } from 'rxjs'; +import { mapTo } from 'rxjs/operators'; + +import { + MockBuilder, + MockRender, + NG_MOCKS_GUARDS, + NG_MOCKS_ROOT_PROVIDERS, + ngMocks, +} from 'ng-mocks'; + +// A simple service simulating login check. +// It will be replaced with its mock copy. +@Injectable() +class LoginService { + public isLoggedIn = false; +} + +// A guard we want to test. +const canMatchGuard: CanMatchFn = (route, segments) => { + if (route && segments && inject(LoginService).isLoggedIn) { + return true; + } + + return from(inject(Router).navigate(['/login'])).pipe(mapTo(false)); +}; + +// Another guard like in a real world example, +// which should be removed from testing to avoid side effects on the route. +const sideEffectGuard: CanMatchFn = () => false; + +// A simple component pretending a login form. +// It will be replaced with a mock copy. +@Component({ + selector: 'login', + template: 'login', +}) +class LoginComponent { + public loginTestRoutingGuardCanMatch() {} +} + +// A simple component pretending a protected dashboard. +// It will be replaced with a mock copy. +@Component({ + selector: 'dashboard', + template: 'dashboard', +}) +class DashboardComponent { + public dashboardTestRoutingGuardCanMatch() {} +} + +// Definition of the routing module. +@NgModule({ + declarations: [LoginComponent, DashboardComponent], + exports: [RouterModule], + imports: [ + RouterModule.forRoot([ + { + component: LoginComponent, + path: 'login', + }, + { + canMatch: [canMatchGuard, sideEffectGuard], + path: '', + children: [ + { + component: DashboardComponent, + path: '**', + }, + ], + }, + ]), + ], + providers: [LoginService], +}) +class TargetModule {} + +describe('TestRoutingGuard:canMatch', () => { + // Because we want to test a canMatch guard, it means that we want to + // test its integration with RouterModule. + // Therefore, RouterModule and the guard should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (yes, with empty routes). + // The module with routes and the guard should be specified + // as the second parameter of MockBuilder. + // Then `NG_MOCKS_GUARDS` should be excluded to remove all guards, + // and `canActivateGuard` should be kept to let you test it. + beforeEach(() => { + return MockBuilder( + [ + RouterModule, + RouterTestingModule.withRoutes([]), + NG_MOCKS_ROOT_PROVIDERS, + ], + [TargetModule], + ) + .exclude(NG_MOCKS_GUARDS) + .keep(canMatchGuard); + }); + + // It is important to run routing tests in fakeAsync. + it('redirects to login', fakeAsync(() => { + if (Number.parseInt(VERSION.major, 10) < 7) { + pending('Need Angular 7+'); + + return; + } + + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // Because by default we are not logged, the guard should + // redirect us /login page. + expect(location.path()).toEqual('/login'); + expect(() => ngMocks.find(LoginComponent)).not.toThrow(); + })); + + it('loads dashboard', fakeAsync(() => { + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + const loginService = ngMocks.get(LoginService); + + // Letting the guard know we have been logged in. + loginService.isLoggedIn = true; + + // First we need to initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // Because now we are logged in, the guard should let us land on + // the dashboard. + expect(location.path()).toEqual('/'); + expect(() => ngMocks.find(DashboardComponent)).not.toThrow(); + })); +}); diff --git a/examples/TestRoutingGuard/test.spec.ts b/examples/TestRoutingGuard/test.spec.ts index bac71d334c..8225ca034c 100644 --- a/examples/TestRoutingGuard/test.spec.ts +++ b/examples/TestRoutingGuard/test.spec.ts @@ -109,7 +109,7 @@ class DashboardComponent { }) class TargetModule {} -describe('TestRoutingGuard', () => { +describe('TestRoutingGuard:test', () => { // Because we want to test the guard, it means that we want to // test its integration with RouterModule. Therefore, we pass // the guard as the first parameter of MockBuilder. Then, to diff --git a/examples/TestRoutingResolver/fn.spec.ts b/examples/TestRoutingResolver/fn.spec.ts new file mode 100644 index 0000000000..b51628a034 --- /dev/null +++ b/examples/TestRoutingResolver/fn.spec.ts @@ -0,0 +1,141 @@ +import { Location } from '@angular/common'; +import { + Component, + inject, + Injectable, + NgModule, +} from '@angular/core'; +import { fakeAsync, tick } from '@angular/core/testing'; +import { + ActivatedRoute, + ResolveFn, + Router, + RouterModule, + RouterOutlet, +} from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { combineLatest, from, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { + MockBuilder, + MockRender, + NG_MOCKS_GUARDS, + NG_MOCKS_RESOLVERS, + NG_MOCKS_ROOT_PROVIDERS, + ngMocks, +} from 'ng-mocks'; + +// A simple service simulating a data request. +@Injectable() +class DataService { + protected flag = true; + + public data(): Observable { + return from([this.flag]); + } +} + +// A resolver we want to test. +const dataResolver: ResolveFn> = () => + combineLatest([inject(DataService).data()]).pipe( + map(([flag]) => ({ flag })), + ); + +// A resolver we want to ignore. +const sideEffectResolver: ResolveFn< + Observable<{ mock: boolean }> +> = () => of({ mock: true }); + +// A dummy component. +// It will be replaced with a mock copy. +@Component({ + selector: 'route', + template: 'route', +}) +class RouteComponent { + public routeTestRoutingFnResolver() {} +} + +// Definition of the routing module. +@NgModule({ + declarations: [RouteComponent], + exports: [RouterModule], + imports: [ + RouterModule.forRoot([ + { + component: RouteComponent, + path: 'route', + resolve: { + data: dataResolver, + mock: sideEffectResolver, + }, + }, + ]), + ], + providers: [DataService], +}) +class TargetModule {} + +describe('TestRoutingResolver:fn', () => { + // Because we want to test a resolver, it means that we want to + // test its integration with RouterModule. + // Therefore, RouterModule and the resolver should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (yes, with empty routes). + // The module with routes and the resolver should be specified + // as the second parameter of MockBuilder. + // Then `NG_MOCKS_RESOLVERS` should be excluded to remove all resolvers, + // and `dataResolver` should be kept to let you test it. + beforeEach(() => { + return MockBuilder( + [ + RouteComponent, // not necessary, added for coverage + RouterModule, + RouterTestingModule.withRoutes([]), + NG_MOCKS_ROOT_PROVIDERS, + ], + TargetModule, + ) + .exclude(NG_MOCKS_GUARDS) + .exclude(NG_MOCKS_RESOLVERS) + .keep(dataResolver); + }); + + // It is important to run routing tests in fakeAsync. + it('provides data to on the route', fakeAsync(() => { + const fixture = MockRender(RouterOutlet, {}); + const router = ngMocks.get(Router); + const location = ngMocks.get(Location); + const dataService = ngMocks.get(DataService); + + // DataService has been replaced with a mock copy, + // let's set a custom value we will assert later on. + dataService.data = () => from([false]); + + // Let's switch to the route with the resolver. + location.go('/route'); + + // Now we can initialize navigation. + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + tick(); // is needed for rendering of the current route. + } + + // Checking that we are on the right page. + expect(location.path()).toEqual('/route'); + + // Let's extract ActivatedRoute of the current component. + const el = ngMocks.find(RouteComponent); + const route = ngMocks.findInstance(el, ActivatedRoute); + + // Now we can assert that it has expected data. + expect(route.snapshot.data).toEqual({ + data: { + flag: false, + }, + }); + })); +}); diff --git a/examples/TestRoutingResolver/test.spec.ts b/examples/TestRoutingResolver/test.spec.ts index a596bfbe04..f560b1bc63 100644 --- a/examples/TestRoutingResolver/test.spec.ts +++ b/examples/TestRoutingResolver/test.spec.ts @@ -15,6 +15,7 @@ import { map } from 'rxjs/operators'; import { MockBuilder, MockRender, + NG_MOCKS_RESOLVERS, NG_MOCKS_ROOT_PROVIDERS, ngMocks, } from 'ng-mocks'; @@ -81,11 +82,7 @@ class RouteComponent { }) class TargetModule {} -// fix for jest without jasmine assertions -const assertion: any = - typeof jasmine === 'undefined' ? expect : jasmine; - -describe('TestRoutingResolver', () => { +describe('TestRoutingResolver:test', () => { // Because we want to test the resolver, it means that we want to // test its integration with RouterModule. Therefore, we pass // the resolver as the first parameter of MockBuilder. Then, to @@ -103,7 +100,7 @@ describe('TestRoutingResolver', () => { NG_MOCKS_ROOT_PROVIDERS, ], TargetModule, - ); + ).exclude(NG_MOCKS_RESOLVERS); }); // It is important to run routing tests in fakeAsync. @@ -135,12 +132,10 @@ describe('TestRoutingResolver', () => { const route: ActivatedRoute = el.injector.get(ActivatedRoute); // Now we can assert that it has expected data. - expect(route.snapshot.data).toEqual( - assertion.objectContaining({ - data: { - flag: false, - }, - }), - ); + expect(route.snapshot.data).toEqual({ + data: { + flag: false, + }, + }); })); }); diff --git a/libs/ng-mocks/src/lib/common/core.tokens.ts b/libs/ng-mocks/src/lib/common/core.tokens.ts index 9e830bca5b..7f9dbdd69e 100644 --- a/libs/ng-mocks/src/lib/common/core.tokens.ts +++ b/libs/ng-mocks/src/lib/common/core.tokens.ts @@ -52,6 +52,16 @@ export const NG_MOCKS_OVERRIDES = new InjectionToken, MetadataO export const NG_MOCKS_GUARDS = new InjectionToken('NG_MOCKS_GUARDS'); (NG_MOCKS_GUARDS as any).__ngMocksSkip = true; +/** + * NG_MOCKS_RESOLVERS token influences on provided resolvers in MockBuilder. + * More info by the links below. + * + * @see https://ng-mocks.sudo.eu/api/MockBuilder#ng_mocks_resolvers-token + * @see https://ng-mocks.sudo.eu/guides/routing-resolver + */ +export const NG_MOCKS_RESOLVERS = new InjectionToken('NG_MOCKS_RESOLVERS'); +(NG_MOCKS_RESOLVERS as any).__ngMocksSkip = true; + /** * NG_MOCKS_INTERCEPTORS token influences on provided interceptors in MockBuilder. * More info by the links below. diff --git a/libs/ng-mocks/src/lib/mock-service/helper.replace-with-mocks.ts b/libs/ng-mocks/src/lib/mock-service/helper.replace-with-mocks.ts index 25d8acf529..90f4dd03eb 100644 --- a/libs/ng-mocks/src/lib/mock-service/helper.replace-with-mocks.ts +++ b/libs/ng-mocks/src/lib/mock-service/helper.replace-with-mocks.ts @@ -1,18 +1,19 @@ -import { NG_MOCKS_GUARDS } from '../common/core.tokens'; +import { NG_MOCKS_GUARDS, NG_MOCKS_RESOLVERS } from '../common/core.tokens'; +import { isNgDef } from '../common/func.is-ng-def'; import ngMocksUniverse from '../common/ng-mocks-universe'; const handleSection = (section: any[]) => { const guards: any[] = []; for (const guard of section) { - if (ngMocksUniverse.isProvidedDef(guard)) { - guards.push(guard); - continue; - } - if (ngMocksUniverse.isExcludedDef(NG_MOCKS_GUARDS)) { + if (!ngMocksUniverse.isProvidedDef(guard) && ngMocksUniverse.isExcludedDef(NG_MOCKS_GUARDS)) { continue; } + guards.push(guard); + if (!isNgDef(guard)) { + ngMocksUniverse.touches.add(guard); + } } return guards; @@ -35,7 +36,7 @@ const handleArray = (cache: Map, value: any[], callback: any): [boolea return [updated, mock]; }; -const handleItemKeys = ['canActivate', 'canActivateChild', 'canDeactivate', 'canLoad']; +const handleItemKeys = ['canActivate', 'canActivateChild', 'canDeactivate', 'canMatch', 'canLoad']; const handleItemGetGuards = (mock: any, section: string) => Array.isArray(mock[section]) ? handleSection(mock[section]) : mock[section]; @@ -66,6 +67,27 @@ const handleItem = ( } } + // Removal of resolvers. + if (typeof mock.resolve === 'object' && mock.resolve) { + const resolve: any = {}; + let resolveUpdated = false; + for (const key of Object.keys(mock.resolve)) { + const resolver = mock.resolve[key]; + if (!ngMocksUniverse.isProvidedDef(resolver) && ngMocksUniverse.isExcludedDef(NG_MOCKS_RESOLVERS)) { + resolveUpdated = resolveUpdated || true; + continue; + } + resolve[key] = resolver; + if (!isNgDef(resolver)) { + ngMocksUniverse.touches.add(resolver); + } + } + if (resolveUpdated) { + updated = updated || true; + mock = { ...mock, resolve }; + } + } + return [updated, mock]; }; diff --git a/package.json b/package.json index 07a36e471d..899023bee4 100644 --- a/package.json +++ b/package.json @@ -103,16 +103,16 @@ "clean:nx": "rm -Rf e2e/nx/node_modules/ng-mocks && rm -Rf e2e/nx/apps/a-nx/src/test", "s:test:e2e": "npm run s:test:a5 && npm run s:test:a6 && npm run s:test:a7 && npm run s:test:a8 && npm run s:test:a9 && npm run s:test:a10 && npm run s:test:a11 && npm run s:test:a12 && npm run s:test:a13 && npm run s:test:a14 && npm run s:test:a15 && npm run s:test:a16 && npm run s:test:nx", "s:test:a5": "npm run s:test:a5es5 && npm run s:test:a5es2015", - "s:test:a5es5": "P=e2e/a5es5/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/test.spec.ts && rm $P/examples/TestRoutingResolver/test.spec.ts && rm $P/tests/issue-4282/test.spec.ts && rm $P/tests/issue-4282/global.spec.ts", - "s:test:a5es2015": "P=e2e/a5es2015/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/test.spec.ts && rm $P/examples/TestRoutingResolver/test.spec.ts && rm $P/tests/issue-4282/test.spec.ts && rm $P/tests/issue-4282/global.spec.ts", - "s:test:a6": "P=e2e/a6/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", - "s:test:a7": "P=e2e/a7/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", - "s:test:a8": "P=e2e/a8/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", - "s:test:a9": "P=e2e/a9/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", - "s:test:a10": "P=e2e/a10/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", - "s:test:a11": "P=e2e/a11/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", - "s:test:a12": "P=e2e/a12/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", - "s:test:a13": "P=e2e/a13/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", + "s:test:a5es5": "P=e2e/a5es5/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/test.spec.ts && rm $P/examples/TestRoutingResolver/test.spec.ts && rm $P/tests/issue-4282/test.spec.ts && rm $P/tests/issue-4282/global.spec.ts && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a5es2015": "P=e2e/a5es2015/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/test.spec.ts && rm $P/examples/TestRoutingResolver/test.spec.ts && rm $P/tests/issue-4282/test.spec.ts && rm $P/tests/issue-4282/global.spec.ts && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a6": "P=e2e/a6/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a7": "P=e2e/a7/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a8": "P=e2e/a8/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a9": "P=e2e/a9/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a10": "P=e2e/a10/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a11": "P=e2e/a11/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a12": "P=e2e/a12/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", + "s:test:a13": "P=e2e/a13/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P && rm $P/examples/TestRoutingGuard/can-*.spec.ts && rm $P/examples/TestRoutingResolver/fn.spec.ts", "s:test:a14": "P=e2e/a14/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", "s:test:a15": "P=e2e/a15/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P", "s:test:a16": "P=e2e/a16/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P",