I'm new to Vue, but have a project with a login page. I can run it fine, but I'm trying to retro-add unit-testing (I know, I'm doing this backwards). I'm using Mocha + Chai and vue test utils. when I try to shallowMount I get an error saying that it cannot read a property. Here is the full error text:
1) Login
Has login text:
TypeError: Cannot read property 'email' of undefined
at Proxy.render (webpack:///./src/views/user/Login.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options:65:34)
at VueComponent.Vue._render (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:3640:22)
at VueComponent.updateComponent (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4168:21)
at Watcher.get (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4582:25)
at new Watcher (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4569:45)
at mountComponent (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4175:3)
at VueComponent.Vue.$mount (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:8512:10)
at init (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:3232:13)
at createComponent (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:6053:9)
at createElm (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:6001:9)
at VueComponent.patch [as __patch__] (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:6611:7)
at VueComponent.Vue._update (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4039:19)
at VueComponent.updateComponent (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4168:10)
at Watcher.get (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4582:25)
at new Watcher (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4569:45)
at mountComponent (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:4175:3)
at VueComponent.Vue.$mount (webpack:///./node_modules/vue/dist/vue.runtime.esm.js?:8512:10)
at mount (webpack:///./node_modules/#vue/test-utils/dist/vue-test-utils.js?:13265:21)
at shallowMount (webpack:///./node_modules/#vue/test-utils/dist/vue-test-utils.js?:13278:10)
at Context.eval (webpack:///./tests/unit/login.spec.js?:17:87)
at processImmediate (internal/timers.js:439:21)
Here is my packages file:
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"test": "mochapack --webpack-config webpack.config.js --require tests/setup.js tests/**/*.spec.js"
},
"dependencies": {
"axios": "^0.19.2",
"core-js": "^3.6.4",
"vue": "^2.6.11",
"vue-axios": "^2.1.5",
"vue-router": "^3.1.5",
"vuelidate": "^0.7.5",
"vuetify": "^2.1.0",
"vuex": "^3.1.2"
},
"devDependencies": {
"#vue/cli-plugin-babel": "^4.2.0",
"#vue/cli-plugin-e2e-cypress": "^4.2.0",
"#vue/cli-plugin-router": "^4.2.0",
"#vue/cli-plugin-unit-mocha": "^4.2.0",
"#vue/cli-plugin-vuex": "^4.2.0",
"#vue/cli-service": "^4.2.0",
"#vue/test-utils": "^1.0.0-beta.31",
"chai": "^4.1.2",
"jsdom": "^16.1.0",
"jsdom-global": "^3.0.2",
"mocha": "^7.0.1",
"mochapack": "^1.1.13",
"node-sass": "^4.12.0",
"sass": "^1.19.0",
"sass-loader": "^8.0.2",
"vue-cli-plugin-vuetify": "^2.0.4",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.3.0"
}
}
Here is my component code:
<template>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex xs12 sm8 md4>
<v-card class="elevation-12">
<v-toolbar color="primary" dark flat>
<v-toolbar-title>Login form</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form #submit.prevent="login">
<v-text-field
autofocus
v-model="email"
label="Email"
prepend-icon="mdi-account-circle"
#blur="$v.email.$touch()"
/>
<div class="red--text text--lighten-1" v-if="$v.email.$error">
<div v-if="!$v.email.required">
<v-icon color="red">mdi-alert-circle-outline</v-icon>
Email is required
</div>
<div v-if="!$v.email.email">
<v-icon color="red">mdi-alert-circle-outline</v-icon>
Invalid email address.
</div>
</div>
<v-text-field
v-model="password"
:type="showPassword ? 'text' : 'password'"
label="Password"
prepend-icon="mdi-lock"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
#click:append="showPassword = !showPassword"
#blur="$v.password.$touch()"
/>
<div
class="red--text text--lighten-1"
v-if="$v.password.$error && !$v.password.required"
>
<v-icon color="red">mdi-alert-circle-outline</v-icon>
Password is required
</div>
<v-btn
type="submit"
color="success"
name="button"
:disabled="$v.$invalid"
>
Login now
</v-btn>
<v-btn
text
small
color="primary"
:to="{ name: 'forgotPassword' }"
>
Forgot your password?
</v-btn>
<div v-if="error" class="red--text text--lighten-1">
<v-icon color="red">mdi-alert-circle-outline</v-icon>
{{ getLoginErrorMsg() }}
</div>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import { required, email } from "vuelidate/lib/validators";
export default {
name: "Login",
data() {
return {
email: "",
password: "",
error: null,
showPassword: false
};
},
validations: {
email: {
required,
email
},
password: {
required
}
},
methods: {
login() {
console.log("Entering component login method");
this.$store
.dispatch("user/login", {
username: this.email,
password: this.password
})
.then(() => {
console.log(
'this.$store.getters["user/passwordChangeRequired"]' +
this.$store.getters["user/passwordChangeRequired"]
);
if (this.$store.getters["user/passwordChangeRequired"]) {
console.log("push to changePassword");
this.$router.push({ name: "changePassword" });
} else {
console.log("pushing to home");
this.$router.push({ name: "home" });
}
})
.catch(err => {
this.error = err.response;
});
},
getLoginErrorMsg() {
if (this.error.status == 401) {
return "Invalid username or password";
} else {
// return `Login failed: {this.error.statusText}`;
return "failed";
}
}
}
};
</script>
<style></style>
Here is the test file:
import { expect } from "chai";
import { mount, shallowMount } from "#vue/test-utils";
import Login from "../../src/views/user/Login.vue";
describe("Login", () => {
it("Has login text", () => {
// const wrapper = mount(Login);
const wrapper = shallowMount(Login);
// not getting here.
expect(2).to.equal(2);
});
});
Note: neither mount or shallowMount works. They both throw the same error.
Here is my webpack.config.js file:
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// this will apply to both plain `.js` files
// AND `<script>` blocks in `.vue` files
{
test: /\.js$/,
loader: 'babel-loader'
},
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
// make sure to include the plugin for the magic
new VueLoaderPlugin()
]
}
I've spent hours googling and I can't really find anything. So, I'm clearly doing something really stupid, because no one else seems to be having this issue. Please tell me why it is throwing this error and unable to shallowMount or mount this vue component.
It appears that my tests/unit/index.js was missing some things... I didn't have vuelidate in there and I believe that was the issue. I changed it to the following and it now works:
// index.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import VueRouter from 'vue-router';
import Vuelidate from "vuelidate";
Vue.config.productionTip = false;
Vue.use(Vuetify);
Vue.use(VueRouter);
Vue.use(Vuelidate);
Related
I'm using cognito for authentication of my app.
In local enviroment everthing it's ok whether launch yarn dev or yarn build and yarn start.
In the amplify server deploy the SSR authentication not working: return always "not authenticated".
This is mine package.json:
{
"name": "xxxxxxxxxxxx",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start -p ${PORT:=3000}",
"lint": "next lint"
},
"dependencies": {
"#fortawesome/fontawesome-svg-core": "^6.1.2",
"#fortawesome/free-regular-svg-icons": "^6.1.2",
"#fortawesome/free-solid-svg-icons": "^6.1.2",
"#fortawesome/react-fontawesome": "^0.1.18",
"#improbable-eng/grpc-web": "^0.14.0",
"#improbable-eng/grpc-web-node-http-transport": "^0.14.0",
"#nivo/bar": "^0.74.0",
"#nivo/core": "^0.74.0",
"#nivo/geo": "^0.74.0",
"#nivo/pie": "^0.74.0",
"#nivo/scatterplot": "^0.74.0",
"#nivo/treemap": "^0.74.0",
"apexcharts": "^3.35.3",
"aws-amplify": "^4.3.34",
"axios": "^0.24.0",
"bootstrap": "^5.2.0",
"cors": "^2.8.5",
"emotion": "^11.0.0",
"eslint-config-next": "^12.2.4",
"google-protobuf": "^3.17.2",
"human-readable-numbers": "^0.9.5",
"jspdf": "^2.5.0",
"next": "12.0.0",
"next-i18next": "^12.0.0",
"rc-slider": "10.0.1",
"react": "^17.0.2",
"react-apexcharts": "^1.4.0",
"react-bootstrap": "^2.4.0",
"react-dom": "^17.0.2",
"react-read-more-read-less": "^1.0.7",
"react-sparklines": "^1.7.0",
"react-toastify": "^9.0.8",
"react-tradingview-widget": "^1.3.2",
"sass": "1.32.13",
"sharp": "^0.29.3",
"swr": "^0.5.6",
"xmlhttprequest": "^1.8.0"
},
"devDependencies": {
"eslint": "7.32.0"
}
}
This is _app.js (pages/_app.js):
import getConfig from "next/config";
import { appWithTranslation } from "next-i18next";
import "../public/app.scss";
import { Amplify } from "aws-amplify";
import awsExports from "../src/aws-exports";
import React from "react";
import AuthContext from "../components/context/AuthContext";
import { Header } from "../components/dashboard/Header";
import { Footer } from "../components/dashboard/Footer";
import { SSRProvider } from "react-bootstrap";
Amplify.configure({ ...awsExports, ssr: true });
export const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
function App({ Component, pageProps }) {
return (
<SSRProvider>
<AuthContext>
<Header />
<Component {...pageProps} />
<Footer />
</AuthContext>
</SSRProvider>
)
}
export default appWithTranslation(App);
The test page (pages/test.js):
import { withSSRContext } from "aws-amplify";
export default function Test({user}) {
return <h5>{user}</h5>
}
export async function getServerSideProps({req}) {
const { Auth } = withSSRContext({ req });
try {
const user = await Auth.currentAuthenticatedUser();
return {
props: {
msg: user.username
},
};
} catch (err) {
console.log(err)
return {
props: {
msg: err
}
}
}
}
That's the error: "The user is not authenticated"
But if I use useEffect in function to retrieve user all is working good.
Oh wow! It's weird. Disabling Restrict access to app resolve the problem (https://docs.aws.amazon.com/amplify/latest/userguide/access-control.html).
Anyone that can answer scientifically?
I have an quasar application that was generated with the quasar-cli.
How do I integrate a unit test into a test runner like Jest for an application like this?
I've added a this to my Jest configuration
"moduleNameMapper": {
"quasar": "<rootDir>/node_modules/quasar-framework"
}
Unfortunately, Jest reports back
Cannot find module 'quasar' from 'index.vue'
Here is the a snippet of the Vue file
<template>
<div style="padding-top: 20px" v-if="refund.type != null ">
<q-btn :label="'Issue ' + ( currency(refund.amount)) + ' Refund'" :disable="refund.amount <= 0" #click="issueRefund()" color="green" class="full-width" :loading="noteLoading" />
</div>
</template>
<script>
import { Notify } from "quasar"; // here is where I am using Quasar
issueRefund() {
this.noteLoading = true;
this.$axios
.post(`${BASE_URL}/issue_refund/?secret=${this.secret}`, {
refund: this.refund,
agent_email: this.userEmail,
order_id: this.selectedOrder.id,
agent_name: this.$route.query.user_name,
order_number: this.selectedOrder.order_number,
ticket_id: this.ticketId
})
.then(res => {
this.noteLoading = false;
if ((res.data.res === "success")) {
Notify.create({
position: "bottom",
type: "positive",
message: "Refund Issued."
});
this.selectedOrder = res.data.order;
this.resetRefundObj();
this.$refs.refundDiag.hide();
} else {
Notify.create({
position: "bottom",
type: "negative",
message: res.data.error
});
}
});
},
</script>
Integrating Jest with Quasar is quite straight-forward. You'll need two packages, babel-jest and jest.
yarn add jest babel-jest -D
After adding those two dependencies, create a jest.config.js file at the root of your project--here's where all the jest configuration goes.
Here's how the jest.config.js file should look like;
module.exports = {
globals: {
__DEV__: true,
},
verbose: false, // false since we want to see console.logs inside tests
bail: false,
testURL: 'http://localhost/',
testEnvironment: 'jsdom',
testRegex: './__unit__/.*.js$',
rootDir: '.',
testPathIgnorePatterns: [
'<rootDir>/components/coverage/',
'<rootDir>/test/cypress/',
'<rootDir>/test/coverage/',
'<rootDir>/dist/',
'<rootDir>/node_modules/',
],
moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper: {
'^vue$': 'vue/dist/vue.common.js',
'quasar': 'quasar-framework/dist/umd/quasar.mat.umd.js',
},
resolver: null,
transformIgnorePatterns: [
'node_modules/core-js',
'node_modules/babel-runtime',
'node_modules/vue',
],
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest',
}
}
Then create a folder inside the root of your project called __unit__
Place a file called MyUnitTest.test.js inside the __unit__ folder. Now Jest picks up files from this folder.
The final touch would be to run the tests, simply add this to the package.json
"unit": "yarn run jest --config jest.config.js"
Boom! -- Now you may run yarn run unit or yarn run unit --watch and it should work.
Here's a sample of a Quasar component and Jest test.
import { createLocalVue, shallowMount } from '#vue/test-utils'
import Vuex from 'vuex'
import Quasar, * as All from 'quasar'
import CookieConsent from '#components/common/CookieConsent.vue'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Quasar, { components: All, directives: All, plugins: All })
describe('CookieConsent.vue', () => {
const wrapper = shallowMount(CookieConsent, {
localVue,
mocks: {
$t: () => {},
},
})
test('CookieConsent.vue mock should exist', () => {
expect(wrapper.exists()).toBe(true)
})
})
Hope you found this useful
I have tried to work out why this test is failing but cannot determine the reason, because I cannot see what the sinon spy object is being called with.
Is there a better way to test sinon.calledWith so it will show the result and expected result?
In the test below the following check passes expect(onLoginClick.called).to.equal(true); but this does not expect(onLoginClick.calledWith(expected)).to.equal(true);.
Any ideas why?
How can I check myself by seeing the actual value onLoginClick is called with vs the expected?
I am running the tests via "npm run test", the project can be cloned and run from https://github.com/Rob-Leggett/react_redux_webpack
Thanks for any answers and time taken to assist with this question.
Test
import React from 'react';
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import Login from '../app/components/login/Login';
describe('<Login/>', function () {
it('should click login button with credentials', () => {
// given
const expected = { username: 'test', password: 'user' };
const errors = [];
const onLoginClick = sinon.spy();
const wrapper = mount(<Login errors={errors} onLoginClick={onLoginClick} />);
// when
wrapper.ref('username').simulate('change', {target: {value: 'test'}});
wrapper.ref('password').simulate('change', {target: {value: 'user'}});
wrapper.find('button').simulate('click');
// then
//expect(onLoginClick.calledWith(expected)).to.equal(true);
expect(onLoginClick.called).to.equal(true);
});
});
Component
import React, { Component, PropTypes } from 'react'
export default class Login extends Component {
renderErrors() {
const { errors } = this.props;
return errors.map((error, i) => {
return (
<p key={i} style={{color:'red'}}>{error}</p>
);
});
}
render() {
return (
<div>
<input type='text' ref='username' className="form-control" style={{ marginRight: '5px' }} placeholder='Username'/>
<input type='password' ref='password' className="form-control" style={{ marginRight: '5px' }} placeholder='Password'/>
<button onClick={() => this.handleLogin()} className="btn btn-primary">
Login
</button>
{this.renderErrors()}
</div>
)
}
handleLogin() {
const { onLoginClick } = this.props;
const credentials = {
username: this.refs.username.value.trim(),
password: this.refs.password.value.trim()
};
onLoginClick(credentials)
}
}
Login.propTypes = {
onLoginClick: PropTypes.func.isRequired,
errors: PropTypes.arrayOf(PropTypes.string)
};
package.json
{
"name": "react_redux_webpack_client",
"version": "1.0.0",
"description": "A ReactJS Client",
"scripts": {
"test": "mocha test/helpers/browser.js test/**/*.spec.js",
"dev": "webpack-dev-server --content-base public/ --hot --inline",
"build": "webpack -p --display-error-details"
},
"repository": {
"type": "git",
"url": "https://github.com/Rob-Leggett/react_redux_webpack.git"
},
"author": "Robert Leggett",
"license": "MIT",
"homepage": "https://github.com/Rob-Leggett/react_redux_webpack",
"bugs": {
"url": "https://github.com/Rob-Leggett/react_redux_webpack/issues"
},
"devDependencies": {
"chai": "^3.5.0",
"css-loader": "^0.26.1",
"enzyme": "^2.7.1",
"extract-text-webpack-plugin": "^1.0.1",
"html-webpack-plugin": "^2.26.0",
"jsdom": "^9.9.1",
"mocha": "^3.2.0",
"node-sass": "^4.3.0",
"react-addons-test-utils": "^15.4.2",
"sass-loader": "^4.1.1",
"sinon": "^1.17.7",
"style-loader": "^0.13.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2"
},
"dependencies": {
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-register": "^6.22.0",
"body-parser": "^1.15.2",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-redux": "^5.0.2",
"redux": "^3.6.0",
"redux-thunk": "^2.2.0",
"whatwg-fetch": "^2.0.1"
}
}
To find out more then true/false in your test, you can can get the args from the Sinon spy like this:
const spyCall = onLoginClick.getCall(0);
expect(spyCall.args[0]).to.equal(expected)
Now the failing test should show you the args you really got.
See http://sinonjs.org/docs/
I am trying to use Parsley validation for an angular2 app I am writing and wants to write some jasmine unit tests. I want to make sure that the input gets validated in the correct way.
I am trying to write a small test, but I think the problem I have is that I can't parsley to load. I'm running a karma runner and have tried to include it in the config files for that.
This is my test file:
///<reference path="./../../../../typings/globals/jasmine/index.d.ts"/>
import { Component, DebugElement, AfterViewInit } from "#angular/core";
import { By } from "#angular/platform-browser";
import { ComponentFixture, TestBed, async } from "#angular/core/testing";
import { FormsModule } from '#angular/forms';
import { ComponentFixtureAutoDetect } from '#angular/core/testing';
import { dispatchEvent } from '#angular/platform-browser/testing/browser-util';
declare var jQuery: any;
describe("StringLengthValidationApp", () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [StringLengthValidationApp],
providers: [
{ provide: ComponentFixtureAutoDetect, useValue: true }
]
});
});
beforeEach(async(() => {
TestBed.compileComponents();
}));
it("should work", () => {
let fixture = TestBed.createComponent(StringLengthValidationApp);
fixture.detectChanges();
return fixture.whenStable().then(() => {
const inputName = 'quick BROWN fox';
let nameInput = fixture.debugElement.query(By.css('input')).nativeElement;
nameInput.value = inputName;
nameInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
let second = fixture.debugElement.query(By.css('textarea')).nativeElement;
second.value = inputName;
second.dispatchEvent(new Event('input'));
fixture.detectChanges();
console.log(fixture.nativeElement);
let errors = fixture.debugElement.queryAll(By.css("ul"));
expect(errors.length).toBe(1);
});
});
});
#Component({
selector: "date-validation-app",
template: `
<form id="form"
class="form-horizontal form-label-left parsleyjs"
data-parsley-validate=""
data-parsley-priority-enabled="false"
novalidate="novalidate">
<input type="text" id="basic" name="basic" class="form-control"
required="required"
data-parsley-trigger="change"
data-parsley-maxlength="3" />
<textarea name="textarea" rows="10" cols="50">Write something here</textarea>
</form>
`
})
class StringLengthValidationApp {
}
My karma.conf.js
var webpackConfig = require('./webpack.test');
module.exports = function (config) {
var _config = {
basePath: '',
frameworks: ['jasmine'],
files: [
{ pattern: './config/karma-test-shim.js', watched: false }
],
preprocessors: {
'./config/karma-test-shim.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
},
webpackServer: {
noInfo: true
},
reporters: ['kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['Chrome'],
singleRun: false
};
config.set(_config);
};
And karma-test-shim.js
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('core-js/es7/reflect');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy');
require('zone.js/dist/sync-test');
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
require('jquery/src/jquery');
require('parsleyjs/dist/parsley.js');
var appContext = require.context('../src', true, /\.spec\.ts/);
appContext.keys().forEach(appContext);
var testing = require('#angular/core/testing');
var browser = require('#angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());
I get an error message like this
Uncaught Error: Error in :0:0 caused by: form.parsley is not a function
TypeError: form.parsley is not a function
When not running with the ngAfterViewInit function trying to bind parsley, it will run but I get the test failing with
Error: Expected 0 to be 1.
And when looking at it in Chrome no validation error is visible either.
My suspicion is that parsley isn't initiated, but I am new to this so I guess I can have made any simple mistake or that it can be anything else that is wrong
Any hints on what do to solve it will be very appreciated
I think I solved the problem by simply adding
'node_modules/parsleyjs/dist/parsley.js',
to the files section in my karma.conf.js
I'm trying to run Angular 2 unit tests on an Angular 2 Component with Jasmine (I am not using Karma, however... just webpacking my code then running the tests in the default Jasmine SpecRunner.html).
When I run my code, I get the error: "A platform with a different configuration has been created. Please destroy it first." Been banging my head on this all day. Reading every post on StackOverflow I can find, but I'm still stuck. Any suggestions?
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed, async, fakeAsync, tick } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "#angular/platform-browser-dynamic/testing";
import {AppLogin} from "../../../app/login/app.login";
describe("Login Component", () => {
let comp: AppLogin;
let fixture: ComponentFixture<AppLogin>;
let el: DebugElement;
function setup() {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
}
setup();
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AppLogin]
});
fixture = TestBed.createComponent(AppLogin);
comp = fixture.componentInstance;
});
it("login form should pass validation", () => {
fixture.detectChanges();
var form = {
EmailAddress: 'test#me.com',
Password: 'test'
};
var validated = comp.formValidated(form);
expect(validated).toBe(true);
});
});
Here is the component I'm attempting to test...
import { Component } from '#angular/core';
#Component({
selector: 'app-login',
template: `
<form *ngIf="active" (ngSubmit)="onSubmit()" class="form-signin">
<h2 class="form-signin-heading">Please sign in</h2>
<label for="EmailAddress" class="sr-only">Email address</label>
<input type="email" name="EmailAddress" id="EmailAddress" class="form-control" placeholder="Email address"
[(ngModel)]="form.EmailAddress" required autofocus>
<label for="Password" class="sr-only">Password</label>
<input type="password" name="Password" id="Password" class="form-control" placeholder="Password" required
[(ngModel)]="form.Password">
<div class="checkbox">
<label>
<input type="checkbox" id="RememberMe" value="remember-me" [(ngModel)]="form.RememberMe"> Remember me
</label>
</div>
<div *ngIf="form.hasError">
<div *ngFor="let error of form.errorMessages" class="alert alert-danger fade in">{{error.message}}</div>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
`
})
export class AppLogin {
form: any;
constructor() {
//
}
formValidated(form: any): boolean {
form.errorMessages = [];
form.hasError = false;
if (form.EmailAddress == null)
form.errorMessages.push({ message: 'Email Address is required.' });
if (form.Password == null)
form.errorMessages.push({ message: 'Password is required.' });
if (form.errorMessages.count > 0)
form.hasError = true;
return !form.hasError;
}
onSubmit(form: any): void {
console.log('Form data: ', form);
}
}
Unfortunately, Jasmine alone did not provide me with the debug information I needed, so I am no longer using Jasmine alone for my unit testing. I am using the recommended Karma/Jasmine setup. (NOTE: However, I am not using the Angular karma-test-shim, which is why I have to run TestBed.initTestEnvironment).
I ran the tests in Karma and I got an error about my component's template. My component template has an angular form. I had to import the angular FormsModule into my test environment. Here is the code which resolved the issue...
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed, async, fakeAsync, tick } from '#angular/core/testing';
import { By, BrowserModule } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "#angular/platform-browser-dynamic/testing";
import { FormsModule } from '#angular/forms';
import {AppLogin} from "../../../app/login/app.login";
describe("Login Component", () => {
let comp: AppLogin;
let fixture: ComponentFixture<AppLogin>;
let el: DebugElement;
beforeEach(() => {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
TestBed.configureTestingModule({
imports: [ FormsModule, BrowserModule ],
declarations: [ AppLogin ]
});
fixture = TestBed.createComponent(AppLogin);
comp = fixture.componentInstance;
});
it("login form should pass validation", () => {
fixture.detectChanges();
var form = {
EmailAddress: 'test#me.com',
Password: 'test'
};
var validated = comp.formValidated(form);
expect(validated).toBe(true);
});
});
I had a bunch of trouble setting up Karma with Webpack originally, but here is a Karma config I wrote, which is working really well for me (and doesn't require the karma-test-shim)...
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'src/tests/tests.ts',
'src/tests/login/app.login.spec.ts'
],
exclude: [
],
preprocessors: {
'src/tests/tests.ts': ['webpack'],
'src/tests/login/app.login.spec.ts': ['webpack', 'sourcemap']
},
webpack: {
devtool: 'inline-source-map',
resolve: {
extensions: ['', '.ts', '.js']
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
},
{
test: /\.ts$/,
loaders: ['ts-loader']
}
]
}
},
webpackMiddleware: {
// webpack-dev-middleware configuration
noInfo: true
},
plugins: [
require("karma-webpack"),
require("karma-jasmine"),
require("karma-chrome-launcher"),
require("karma-sourcemap-loader"),
require("karma-spec-reporter")
],
reporters: ['spec'],
port: 9876,
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true
});
};
And finally, here's the code for the tests.ts file I included in my Karma config. This is where I require() all the code I need to run angular tests...
require('zone.js/dist/zone');
require('reflect-metadata');
require('rxjs');
require('#angular/platform-browser');
require('#angular/platform-browser-dynamic');
require('#angular/core');
require('#angular/common');
require('#angular/http');
require('#angular/router');
Error.stackTraceLimit = Infinity;
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy'); // since zone.js 0.6.15
require('zone.js/dist/sync-test');
require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
var testing = require('#angular/core/testing');