Skip to content

Testing Components with TestBed #1175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
May 16, 2018
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f74f38a
feat(testing): support TestBed and export jasmine/mocha zone scripts
justindujardin Oct 23, 2017
2fe5bea
chore: update for @angular ~5.1.0
justindujardin Jan 9, 2018
60fde19
chore: do not upgrade zone.js version
justindujardin Jan 9, 2018
502d9a6
Rebase to 5.2.0
hypery2k Jan 29, 2018
68960da
Merge branch 'master' into testing
hypery2k Feb 4, 2018
43fb66b
Merge branch 'master' into testing
hypery2k Feb 8, 2018
1c453f1
Merge branch 'master' into testing
hypery2k Mar 2, 2018
73baa48
Merge branch 'master' into testing
hypery2k Mar 15, 2018
6dcc1dd
Merge branch 'master' into testing
hypery2k Mar 22, 2018
49009c9
Merge branch 'master' into testing
hypery2k Apr 14, 2018
7dd3510
chore(tests): Fix Renderer Tests
Apr 14, 2018
7a06a03
Merge branch 'master' into testing
hypery2k Apr 24, 2018
e00e8a0
Merge branch 'master' into testing
hypery2k May 3, 2018
b145288
fix: rxjs6 imports
May 3, 2018
941307a
refactor: Remove snippets tests
May 3, 2018
e588551
Merge pull request #1 from vakrilov/testing
hypery2k May 4, 2018
99afad2
chore(build): Allow CI testing
hypery2k May 9, 2018
c0eb376
fix(compat-error): Resolve RxJS issue
hypery2k May 9, 2018
523e2d7
feat(zone): Update zonejs to 0.8.26
May 9, 2018
1ad6486
Merge pull request #2 from vakrilov/bugfix/testing
hypery2k May 9, 2018
3c82897
chore(tests): Unit tests made green again
May 10, 2018
9f8383b
Merge pull request #3 from vakrilov/bugfix/testing
hypery2k May 10, 2018
67baa31
Merge branch 'master' into testing
May 10, 2018
0edad68
chore(test): Provide NSLocationStrategy for modal tests
May 10, 2018
5619247
Merge pull request #4 from vakrilov/testing
hypery2k May 10, 2018
c89d617
fix(zone): Fix zone js patching Promise prop descriptr and breaking a…
May 15, 2018
66f07c0
Merge pull request #6 from vakrilov/testing
hypery2k May 15, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ tags
!/nativescript-angular/postinstall.js
!/nativescript-angular/hooks/**/*.js
!/nativescript-angular/gulpfile.js
!/nativescript-angular/zone-js/**/*.js
!/nativescript-angular/zone-js/dist/*.js

.tscache
.nvm
Expand Down
1 change: 1 addition & 0 deletions nativescript-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,4 @@
"zone.js": "^0.8.12"
}
}

6 changes: 5 additions & 1 deletion nativescript-angular/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "@angular/core";

import { Device } from "tns-core-modules/platform";
import { View } from "tns-core-modules/ui/core/view";
import { View, getViewById } from "tns-core-modules/ui/core/view";
import { addCss } from "tns-core-modules/application";
import { profile } from "tns-core-modules/profiling";

Expand Down Expand Up @@ -109,6 +109,10 @@ export class NativeScriptRenderer extends Renderer2 {
@profile
selectRootElement(selector: string): NgView {
traceLog("NativeScriptRenderer.selectRootElement: " + selector);
if (selector && selector[0] === "#") {
const result = getViewById(this.rootView, selector.slice(1));
return (result || this.rootView) as NgView;
}
return this.rootView;
}

Expand Down
29 changes: 29 additions & 0 deletions nativescript-angular/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { NgModule } from "@angular/core";
import { TestComponentRenderer } from "@angular/core/testing";
import { NativeScriptTestComponentRenderer } from "./src/nativescript_test_component_renderer";
import { COMMON_PROVIDERS } from "../platform-common";
import { APP_ROOT_VIEW } from "../platform-providers";
import { testingRootView } from "./src/util";
export * from "./src/util";

/**
* Providers array is exported for cases where a custom module has to be constructed
* to test a particular piece of code. This can happen, for example, if you are trying
* to test dynamic component loading and need to specify an entryComponent for the testing
* module.
*/
export const NATIVESCRIPT_TESTING_PROVIDERS: any[] = [
COMMON_PROVIDERS,
{provide: APP_ROOT_VIEW, useFactory: testingRootView},
{provide: TestComponentRenderer, useClass: NativeScriptTestComponentRenderer},
];

/**
* NativeScript testing support module. Enables use of TestBed for angular components, directives,
* pipes, and services.
*/
@NgModule({
providers: NATIVESCRIPT_TESTING_PROVIDERS
})
export class NativeScriptTestingModule {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable } from "@angular/core";
import { TestComponentRenderer } from "@angular/core/testing";
import { topmost } from "tns-core-modules/ui/frame";
import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container";

/**
* A NativeScript based implementation of the TestComponentRenderer.
*/
@Injectable()
export class NativeScriptTestComponentRenderer extends TestComponentRenderer {

insertRootElement(rootElId: string) {
const page = topmost().currentPage;

const layout = new ProxyViewContainer();
layout.id = rootElId;

const rootLayout = page.layoutView as LayoutBase;
rootLayout.addChild(layout);
}

}

165 changes: 165 additions & 0 deletions nativescript-angular/testing/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@

import { View } from "tns-core-modules/ui/core/view";
import { topmost } from "tns-core-modules/ui/frame";
import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NgModule, Type } from "@angular/core";
import { NativeScriptModule } from "../../nativescript.module";
import { platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing";
import { NS_COMPILER_PROVIDERS } from "../../platform";
import { NATIVESCRIPT_TESTING_PROVIDERS, NativeScriptTestingModule } from "../index";
import { CommonModule } from "@angular/common";
/**
* Get a reference to the root application view.
*/
export function testingRootView(): View {
return topmost().currentPage.content;
}

/**
* Declared test contexts. When the suite is done this map should be empty if all lifecycle
* calls have happened as expected.
* @private
*/
const activeTestFixtures: ComponentFixture<any>[][] = [];

/**
* Return a promise that resolves after (durationMs) milliseconds
*/
export function promiseWait(durationMs: number) {
return () => new Promise((resolve) => setTimeout(() => resolve(), durationMs));
}

/**
* Perform basic TestBed environment initialization. Call this once in the main entry point to your tests.
*/
export function nsTestBedInit() {
TestBed.initTestEnvironment(
NativeScriptTestingModule,
platformBrowserDynamicTesting(NS_COMPILER_PROVIDERS)
);
}

/**
* Helper for configuring a TestBed instance for rendering components for test. Ideally this
* would not be needed, and in truth it's just a wrapper to eliminate some boilerplate. It
* exists because when you need to specify `entryComponents` for a test the setup becomes quite
* a bit more complex than if you're just doing a basic component test.
*
* More about entryComponents complexity: https://github.com/angular/angular/issues/12079
*
* Use:
* ```
* beforeEach(nsTestBedBeforeEach([MyComponent,MyFailComponent]));
* ```
*
* **NOTE*** Remember to pair with {@see nsTestBedAfterEach}
*
* @param components Any components that you will create during the test
* @param providers Any services your tests depend on
* @param imports Any module imports your tests depend on
* @param entryComponents Any entry components that your tests depend on
*/
export function nsTestBedBeforeEach(
components: any[],
providers: any[] = [],
imports: any[] = [],
entryComponents: any[] = []) {
return (done) => {
activeTestFixtures.push([]);
// If there are no entry components we can take the simple path.
if (entryComponents.length === 0) {
TestBed.configureTestingModule({
declarations: [...components],
providers: [...providers],
imports: [NativeScriptModule, ...imports]
});
} else {
// If there are entry components, we have to reset the testing platform.
//
// There's got to be a better way... (o_O)
TestBed.resetTestEnvironment();
@NgModule({
declarations: entryComponents,
exports: entryComponents,
entryComponents: entryComponents
})
class EntryComponentsTestModule {
}
TestBed.initTestEnvironment(
EntryComponentsTestModule,
platformBrowserDynamicTesting(NS_COMPILER_PROVIDERS)
);
TestBed.configureTestingModule({
declarations: components,
imports: [
NativeScriptModule, NativeScriptTestingModule, CommonModule,
...imports
],
providers: [...providers, ...NATIVESCRIPT_TESTING_PROVIDERS],
});
}
TestBed.compileComponents()
.then(() => done())
.catch((e) => {
console.log(`Failed to instantiate test component with error: ${e}`);
console.log(e.stack);
done();
});
};
}

/**
* Helper for a basic component TestBed clean up.
* @param resetEnv When true the testing environment will be reset
* @param resetFn When resetting the environment, use this init function
*/
export function nsTestBedAfterEach(resetEnv = true, resetFn = nsTestBedInit) {
return () => {
if (activeTestFixtures.length === 0) {
throw new Error(
`There are no more declared fixtures.` +
`Did you call "nsTestBedBeforeEach" and "nsTestBedAfterEach" an equal number of times?`
);
}
const root = testingRootView() as LayoutBase;
const fixtures = activeTestFixtures.pop();
fixtures.forEach((fixture) => {
root.removeChild(fixture.nativeElement);
fixture.destroy();
});
TestBed.resetTestingModule();
if (resetEnv) {
TestBed.resetTestEnvironment();
resetFn();
}
};
}

/**
* Render a component using the TestBed helper, and return a promise that resolves when the
* ComponentFixture is fully initialized.
*/
export function nsTestBedRender<T>(componentType: Type<T>): Promise<ComponentFixture<T>> {
const fixture = TestBed.createComponent(componentType);
fixture.detectChanges();
return fixture.whenRenderingDone()
// TODO(jd): it seems that the whenStable and whenRenderingDone utilities of ComponentFixture
// do not work as expected. I looked at how to fix it and it's not clear how to provide
// a {N} specific subclass, because ComponentFixture is newed directly rather than injected
// What to do about it? Maybe fakeAsync can help? For now just setTimeout for 100ms (x_X)
.then(promiseWait(100))
.then(() => {
const list = activeTestFixtures[activeTestFixtures.length - 1];
if (!list) {
console.warn(
"nsTestBedRender called without nsTestBedBeforeEach/nsTestBedAfter each. " +
"You are responsible for calling 'fixture.destroy()' when your test is done " +
"in order to clean up the components that are created."
);
} else {
list.push(fixture);
}
return fixture;
});
}
10 changes: 10 additions & 0 deletions nativescript-angular/zone-js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Zone.js for NativeScript
---

Zone.js is a library that aims to intercept all asynchronous API calls made in an environment, in order
to wrap them into coherent execution contexts over time.

NativeScript executes inside an environment that Zone.js is not designed to work in, so a custom Zone.js output
must be created.

Find out more about this in the [Upgrading Zone.js document](../../doc/upgrading-zonejs.md)
Loading