diff --git a/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.spec.ts b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.spec.ts
new file mode 100644
index 0000000..7eee369
--- /dev/null
+++ b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.spec.ts
@@ -0,0 +1,29 @@
+///
+import { assert } from 'chai';
+import { CounterComponent } from './counter.component';
+import { TestBed, async, ComponentFixture } from '@angular/core/testing';
+
+let fixture: ComponentFixture;
+
+describe('Counter component', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({ declarations: [CounterComponent] });
+ fixture = TestBed.createComponent(CounterComponent);
+ fixture.detectChanges();
+ });
+
+ it('should display a title', async(() => {
+ const titleText = fixture.nativeElement.querySelector('h2').textContent;
+ expect(titleText).toEqual('Counter');
+ }));
+
+ it('should start with count 0, then increments by 1 when clicked', async(() => {
+ const countElement = fixture.nativeElement.querySelector('strong');
+ expect(countElement.textContent).toEqual('0');
+
+ const incrementButton = fixture.nativeElement.querySelector('button');
+ incrementButton.click();
+ fixture.detectChanges();
+ expect(countElement.textContent).toEqual('1');
+ }));
+});
diff --git a/templates/Angular2Spa/ClientApp/test/boot-tests.ts b/templates/Angular2Spa/ClientApp/test/boot-tests.ts
new file mode 100644
index 0000000..6121b04
--- /dev/null
+++ b/templates/Angular2Spa/ClientApp/test/boot-tests.ts
@@ -0,0 +1,32 @@
+// Load required polyfills and testing libraries
+import 'angular2-universal-polyfills';
+import 'zone.js/dist/long-stack-trace-zone';
+import 'zone.js/dist/proxy.js';
+import 'zone.js/dist/sync-test';
+import 'zone.js/dist/jasmine-patch';
+import 'zone.js/dist/async-test';
+import 'zone.js/dist/fake-async-test';
+import * as testing from '@angular/core/testing';
+import * as testingBrowser from '@angular/platform-browser-dynamic/testing';
+
+// There's no typing for the `__karma__` variable. Just declare it as any
+declare var __karma__: any;
+declare var require: any;
+
+// Prevent Karma from running prematurely
+__karma__.loaded = function () {};
+
+// First, initialize the Angular testing environment
+testing.getTestBed().initTestEnvironment(
+ testingBrowser.BrowserDynamicTestingModule,
+ testingBrowser.platformBrowserDynamicTesting()
+);
+
+// Then we find all the tests
+const context = require.context('../', true, /\.spec\.ts$/);
+
+// And load the modules
+context.keys().map(context);
+
+// Finally, start Karma to run the tests
+__karma__.start();
diff --git a/templates/Angular2Spa/ClientApp/test/karma.conf.js b/templates/Angular2Spa/ClientApp/test/karma.conf.js
new file mode 100644
index 0000000..e8162c4
--- /dev/null
+++ b/templates/Angular2Spa/ClientApp/test/karma.conf.js
@@ -0,0 +1,25 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/0.13/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '.',
+ frameworks: ['jasmine'],
+ files: [
+ '../../wwwroot/dist/vendor.js',
+ './boot-tests.ts'
+ ],
+ preprocessors: {
+ './boot-tests.ts': ['webpack']
+ },
+ reporters: ['progress'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ webpack: require('../../webpack.config.js').filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser
+ webpackMiddleware: { stats: 'errors-only' }
+ });
+};
diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json
index 2be2bed..b0987c9 100644
--- a/templates/Angular2Spa/package.json
+++ b/templates/Angular2Spa/package.json
@@ -1,6 +1,9 @@
{
"name": "Angular2Spa",
"version": "0.0.0",
+ "scripts": {
+ "test": "karma start ClientApp/test/karma.conf.js"
+ },
"dependencies": {
"@angular/common": "2.0.2",
"@angular/compiler": "2.0.2",
@@ -42,5 +45,17 @@
"webpack-merge": "^0.14.1",
"webpack-node-externals": "^1.4.3",
"zone.js": "^0.6.25"
+ },
+ "devDependencies": {
+ "@types/chai": "^3.4.34",
+ "@types/jasmine": "^2.5.37",
+ "chai": "^3.5.0",
+ "jasmine-core": "^2.5.2",
+ "karma": "^1.3.0",
+ "karma-chai": "^0.1.0",
+ "karma-chrome-launcher": "^2.0.0",
+ "karma-cli": "^1.0.1",
+ "karma-jasmine": "^1.0.2",
+ "karma-webpack": "^1.8.0"
}
}
diff --git a/templates/Angular2Spa/webpack.config.js b/templates/Angular2Spa/webpack.config.js
index 33e002f..ea40466 100644
--- a/templates/Angular2Spa/webpack.config.js
+++ b/templates/Angular2Spa/webpack.config.js
@@ -7,6 +7,7 @@ var allFilenamesExceptJavaScript = /\.(?!js(\?|$))([^.]+(\?|$))/;
// Configuration in common to both client-side and server-side bundles
var sharedConfig = {
+ context: __dirname,
resolve: { extensions: [ '', '.js', '.ts' ] },
output: {
filename: '[name].js',