How can I automate both E2E and unit tests with Yeoman & AngularJS? - angularjs

I'm using Yeoman and generator-angular to manage AngularJS apps, but I'm having trouble with automated testing.
Running grunt test will run unit tests once. I can get E2E tests to run after unit tests by altering the karma config block in Gruntfile.js, adding e2e:
karma: {
//...
e2e: {
configFile: 'karma-e2e.conf.js',
singleRun: true
}
},
Great: now when I type grunt test all tests are run. But they're only run one time, and there's a big overhead (starting compass, running the server, launching the Chrome processes, etc.). Instead, the server and Chrome processes should remain running and, when I save a test, tests should be re-run.
I can achieve this by modifying both karma.conf.js and karma-e2e.conf.js and setting singleRun = true, then running karma start in one terminal pane, and karma start karma-e2e.conf.js in another. Provided none of the ports in the karma configs conflict (which they do by default), this works. Now I'm bypassing Grunt and just doing my own thing (which seems a little silly, as Grunt is supposed to make things easier).
Anyway, after a few more changes (fixes?) — not detailed for brevity — this works but doesn't cut it: I now have to run two different commands and keep an eye on two different terminal panes. Surely there's a better way.
How can I run a single command to watch my test files and re-run tests appropriately?
Bonus question: why on Earth is this functionality not provided as is? Is it just a question of the developer(s) of generator-angular not having enough time to implement this stuff? I ask because I'm only just getting into Angular/Yeoman/Karma (as you probably noticed), and feel that automated testing of both E2E and unit tests are crucial to workflow.

As I mentioned in a comment to your question - PhantomJS saves a lot of hassle. That aside, I believe you can handle everything from within your Gruntfile and just continue to run grunt test to start the whole thing.
grunt-karma allows full customization of your karma options with some handy add-ons.
From the docs:
....
You can override any of the config file's settings directly:
karma: {
unit: {
configFile: 'karma.conf.js',
runnerPort: 9999,
singleRun: true,
browsers: ['PhantomJS']
}
}
Sharing Configs
If you have multiple targets, it may be helpful to share common
configuration settings between them. Grunt-karma supports this by
using the options property:
karma: {
options: {
configFile: 'karma.conf.js',
runnerPort: 9999,
browsers: ['Chrome', 'Firefox']
},
continuous: {
singleRun: true
browsers: ['PhantomJS']
},
dev: {
reporters: 'dots'
}
}
Additionally you may want to snoop around in Yeoman's generator-angular Gruntfile code to see what else may be available or at least mockable.

You can try this to run only e2e tests
grunt karma:e2e

Within the karma.conf.js file (approx line:38) find autoWatch = false; and change it to true.
Now if you run grunt karma:unit you will find that it leaves the test server running and any changes to project files immediately run the tests again.

//
// test/midway/appSpec.js
//
describe("Midway: Testing Modules", function() {
describe("App Module:", function() {
var module;
before(function() {
module = angular.module("App");
});
it("should be registered", function() {
expect(module).not.to.equal(null);
});
describe("Dependencies:", function() {
var deps;
var hasModule = function(m) {
return deps.indexOf(m) >= 0;
};
before(function() {
deps = module.value('appName').requires;
});
//you can also test the module's dependencies
it("should have App.Controllers as a dependency", function() {
expect(hasModule('App.Controllers')).to.equal(true);
});
it("should have App.Directives as a dependency", function() {
expect(hasModule('App.Directives')).to.equal(true);
});
it("should have App.Filters as a dependency", function() {
expect(hasModule('App.Filters')).to.equal(true);
});
it("should have App.Routes as a dependency", function() {
expect(hasModule('App.Routes')).to.equal(true);
});
it("should have App.Services as a dependency", function() {
expect(hasModule('App.Services')).to.equal(true);
});
});
});
});

Related

Starting protractor through gulp

I'm trying to run protractor as a gulp task but I can't get it to work.
I've tried the gulp-protractor plugin like this:
gulp.task('protractor-server', function (done) {
var called = false;
nodemon({
script: 'test/e2e/server/server.js',
stdout: true,
ignore: ['app/**', 'node_modules'],
watch: 'test/e2e/**/*.js'
})
.on('start', function () {
if (!called) {
done();
}
called = true;
});
});
gulp.task('run-protractor', ['protractor-server'], function (done) {
return gulp.src(['test/e2e/**/*.js'])
.pipe(protractor({
configFile: __dirname + '/protractor.conf.js'
}))
.on('error', function (error) {
console.log('gulp error: ', error);
throw error;
});
});
However, firstly why do I need to use gulp.src(['test/e2e/**/*.js']) and then pipe protractor? Is it not possible to run protractor by it self since i have specified the spec files in the protractor.conf-file. (fyi I did try that but it didn't work).
Secondly, when I try and run like specified in the above snippet I keep getting errors like this: WARNING - pattern C:\[absolutepath]\test\e2e\[subfolder]\[filename].js did not match any files. for all files that exist in the e2e folder and sub-folders. What could be the reason for this?
this is my protractor conf file:
exports.config = {
specs: [
'test/e2e/[subfolder]/*.page.js',
'test/e2e/[subfolder]/*.spec.js'
],
baseUrl: 'http://localhost:3000'
};
If I start the server separately and the run protractor from the command prompt it works fine. I was thinking to use child_process.spawn and start a protractor child process but i haven't gotten that to work either. Any suggestions on how to start protractor from a gulp task?
No need to use 'test/e2e/**/*.js']
Provide all configuration in protractor.conf.js file it self.
You can use task like
gulp.task('run-protractor', ['run-server','run-webdriver'], function (done) {
//run protractor conf here
})
create run-server and run-webdriver gulp taks, test them separately once they are working, utilize them into run-protractor task.

Gulp-protractor keep saying "Spec patterns did not match any files"

I'm trying to run my end to end tests written with protractor and jasmine. It works perfectly when I call protractor protractor.config.js directly.
However, when I use gulp-protractor, I keep getting the "Spec patterns did not match any files" error and the tests do not run.
This is my protractor runner gulp task:
gulp.task('protractor-run', function (done) {
return gulp.src(["./e2e-tests/**/*-spec.js"])
.pipe(protractor({
configFile: "./config/protractor-config.js",
args: ['--baseUrl', 'http://127.0.0.1:8000']
}))
.on('error', function(e) { throw e })
});
and this is the error:
WARNING - pattern C:\path\to\app\e2e-tests\login\login-spec.js did not math any files.
[launcher] Process exited with error code 1
C:\path\to\app\node_modules\protractor\node_modules\q\q.js:126
throw e;
^
Error: Spec patterns did not match any files.
What am I missing?
I managed to get it working. By providing an empty readable stream. Then you specify your spec files in the config file instead.
var protractor = require('gulp-protractor').protractor;
gulp.task('protractor', ['webdriverUpdate'],function(){
return gulp.src([])
.pipe(protractor({
configFile: __dirname + '/protractor.conf.js'
}));
});
also don't forget the webdriverUpdate
var webdriverUpdate = require('gulp-protractor').webdriver_update;
gulp.task('webdriverUpdate', webdriverUpdate );
and in the config file this:
seleniumServerJar: './node_modules/protractor/selenium/selenium-server-standalone-2.47.1.jar',
With this I stopped getting the error.
Update
The issue #2551 is closed and fixed since 2.5.0
I resolved this in a gulpfile that launches protractor tests by putting a file path into the parameter of gulp.src(['file_path_goes_here']). The task I was trying to run had no file path between the brackets, and was throwing the error.
gulp.task('works', 'Run some tests', function() {
gulp.src(['path/to/test.spec.js'])
.pipe(protractor({
configFile: __dirname + '/../test/protractor.conf.js',
args: ['--baseUrl', 'http://localhost:9099']
}))
});
gulp.task('error', 'Run feature tests locally', function() {
gulp.src([''])
.pipe(protractor({
configFile: __dirname + '/../test/protractor_local.conf.js',
args: ['--baseUrl', 'http://localhost:9099']
}))
});

How to test an Angular app?

For simple application such as just a "Hello World" where do I write tests.
I have created a plnkr.
http://plnkr.co/edit/M0GAIE837G3s1vNwTyK8?p=info
Now this is a very simple plnkr, which does nothing but display Hello World.
Now if I want to write a test for this Application i.e for MainCtrl.. where do I plug it in ?
To run test with Angular-Karma-Jasmine:
You need to install nodejs, karma runs on top of node
You need to install karma from Node Packaged Modules from your command window execute: npm install -g karma
If you plan to run this with Chrome and Firefox and you are running this on windows you need to add 2 environment variables:
CHROME_BIN = [Crome installation path/chrome.exe]
FIREFOX_BIN =[Firefox installation path/firefox.exe]
4. Go back to your project folder using the command window once there you can execute:karma init
Just hit enter until it finishes; bottom line this will create a file named: karma.config.js
In my project this file looks like this, yours probably will include some helpful comments on the different settings:
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'../app/*.js',
'../app/lib/angular.js',
'../app/lib/angular-route.min.js',
'../app/lib/angular-mocks.js',
'../app/app.js',
'controllers/*.js',
'services/*.js',
],
exclude: [
],
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome','Firefox'],
captureTimeout: 60000,
singleRun: false
});
};
Important: make sure you included angular-mocks in your configuration, the inject function is on that module.
5. Go back to your command window, navigate where your karma.config.js file is and and execute: karma start
At this point you will be good to go to start writing tests with jasmine.
a simple jasmine test for your controller will be:
describe('MainCtrl', function() {
var $scope, $rootScope, createController;
beforeEach(inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('MainCtrl', {
'$scope': $scope
});
};
}));
it('should have a...', function() {
var controller = createController();
// do your testing here...
});
});

Unit testing custom $animate animations in karma

I'm trying to run a unit test that tests a custom addClass animation. No matter what I try, including running samples off the web, do custom animations run in the karma test runner. NOTE my animation code works fine in a working site. I just want to be able to generate jasmine tests for my animations.
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'Scripts\\angular.js',
'Scripts\\angular-animate.js',
'Scripts\\angular-mocks.js',
'Scripts\\animate-scr.js',
'Scripts\\animate-test.js',
],
exclude: [
],
reporters: ['progress'],
port: 9875,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
captureTimeout: 60000,
singleRun: false
});
};
Here is my source code for the animation.
var counter = 0;
var testapp = angular.module('testapp', ['ngAnimate']);
testapp.counter = 2;
testapp.animation('.footeropen', function() {
return {
addClass : function(element, className) {
testapp.counter = 3;
}
};
});
And here is my unit tests code
describe('Testing Animations', function() {
beforeEach(module('testapp'));
beforeEach(module('mock.animate'));
it("should synchronously test the animation",
inject(function($animate, $rootScope) {
var element = angular.element('<div>hello</div>');
$animate.addClass(element, 'footeropen');
$rootScope.$digest();
expect(testapp.counter).toBe(3);
}));
});
Its with disappointment that I discovered that the mocked animate object actively stops custom animations on addClass() and removeClass(). The exact comment in the code is:
//There is no point in perform a class-based animation if the element already contains
//(on addClass) or doesn't contain (on removeClass) the className being animated.
//The reason why this is being called after the previous animations are cancelled
//is so that the CSS classes present on the element can be properly examined.
I have looked at running it without the mocked animate object and this causes a similar response.
So testing at this point is not possible.

How to configure the loading order of my Sencha Touch app files when testing with Jasmine

I would like to setup a proper testing environment for a Sencha Touch 2 app using jasmine.
I used the first three parts of this tutorial for my first steps:
https://content.pivotal.io/blog/sencha-touch-bdd-part-1
https://content.pivotal.io/blog/sencha-touch-bdd-part-2
https://content.pivotal.io/blog/sencha-touch-bdd-part-3-testing-views-and-mocking-stores
My actual problem is the following:
two config entries for two of my classes (one store and one view) need to call methods / read properties of my main app object respectively the Ext.Viewport object.
Concrete:
1.) one of my stores reads a value on the main namespace of my app (MyAppName.app.backendUrl)
Ext.define('MyAppName.store.MyStore', {
extend: 'Ext.data.Store',
config: {
model: 'MyAppName.model.MyModel',
proxy: {
type: 'ajax',
url: MyAppName.app.backendUrl + '/data.json',
reader: 'json'
},
autoLoad: true
}
});
2.) one of my views does call a method (Ext.Viewport.getOrientation()) on Ext.Viewport:
Ext.define('MyAppName.view.LoginView', {
extend: 'Ext.form.Panel',
alias: "widget.loginview",
config: {
title: 'Login',
items: [
{
xtype: 'image',
src: Ext.Viewport.getOrientation() == 'portrait' ? '../../../img/login.png' : '../../../img/login-small.png',
style: Ext.Viewport.getOrientation() == 'portrait' ? 'width:80px;height:80px;margin:auto' : 'width:40px;height:40px;margin:auto'
}
]
}
});
Unfortunately, this crashes, because both objects (MyAppName and Ext.Viewport) are not yet defined when these calls are made.
This is only the case for the testing setup (as the tutorial outlines, there is a specific app.js just for the testing). When I run the actual app in the browser (via the 'normal' app.js), this problem does not occur.
How could this be fixed (so: how can I make sure that my views/store files are run AFTER MyAppname.app and Ext.Viewport already exist)?
Thanks a lot.
I found that running Ext.application usually opens views that you typically don't want during a unit test - otherwise you're venturing into integration testing so I avoid use of the Sencha development loader. Instead I use Karma to load the unit tests and application class files. You configure these files inside the karma.conf.js file (example below).
I've adapted the examples from the excellent unit test tutorials from Pivotal Labs. Since Karma has a built in web server you don't need Rails, Rake or pow as their 1st tutorial describes. Using Karma means you can easily integrate your unit tests with Javascript tools like IntelliJ IDEA or WebStorm as well as CI systems and cloud testing like https://saucelabs.com/ . You can also configure it to watch your code files and auto rerun unit tests when you update them. You can also use karma-istanbul to perform code coverage analysis.
Using a trick I learned here, I run a setup.js file that is configured in my karma.conf.js file to load before the unit tests. It creates a fake application object so that controllers can assign themselves to an application instance and it intentionally does not have a launch() method. It also include the SpecHelper.js code from the Pivotal Labs example.
// Create (but don't launch) the app
Ext.application({name: 'MyAppName' });
For the view unit test question, you can create a fake Ext.Viewport object and add a spyOn().andReturn() to fake the Ext.Viewport.getOrientation() method required by the view during testing. This then means your unit tests can easily cover both orientation cases. You also add a renderTo: property during testing to inspect the rendered view:
describe("when portrait orientation", function() {
var view;
beforeEach(function () {
if (!Ext.Viewport) Ext.Viewport = {};
spyOn(Ext.Viewport, 'getOrientation').andReturn('portrait');
view = Ext.create('MyAppName.view.LoginView', {
renderTo: 'jasmine_content'
}
}
it("should render large image", function() {
expect(Ext.DomQuery.select('...')).toContain('img/login.png');
});
it("should render 80px style", function() {
expect(Ext.DomQuery.select('...')).toContain('80px');
});
});
View unit tests (explains how to use the renderTo property).
https://content.pivotal.io/blog/sencha-touch-bdd-part-3-testing-views-and-mocking-stores
My setup.js file show below, includes code from SpecHelper.js shown here.
You'll need this to use the renderTo property.
https://content.pivotal.io/blog/sencha-touch-bdd-part-1
Controller unit tests covers how to connect a controller to your fake application instance.
https://content.pivotal.io/blog/sencha-touch-bdd-part-5-controller-testing
setup.js
This code steals a Karma loading trick from here but unlike their example it avoids use of the development loader.
Ext.Loader.setConfig({
enabled: true, // Turn on Ext.Loader
disableCaching: false // Turn OFF cache BUSTING
});
// 'base' is set by Karma to be __dirname of karm.conf.js file
Ext.Loader.setPath({
'Ext': 'base/touch/src',
'MyAppName': 'base/app'
});
// Create (but don't launch) the app
Ext.application({name: 'MyAppName' });
Ext.require('Ext.data.Model');
afterEach(function () {
Ext.data.Model.cache = {}; // Clear any cached models
});
var domEl;
beforeEach(function () { // Reset the div with a new one.
domEl = document.createElement('div');
domEl.setAttribute('id', 'jasmine_content');
var oldEl = document.getElementById('jasmine_content');
if (oldEl) oldEl.parentNode.replaceChild(domEl, oldEl);
});
afterEach(function () { // Make the test runner look pretty
domEl.setAttribute('style', 'display:none;');
});
// Karma normally starts the tests right after all files specified in 'karma.config.js' have been loaded
// We only want the tests to start after Sencha Touch/ExtJS has bootstrapped the application.
// 1. We temporary override the '__karma__.loaded' function
// 2. When Ext is ready we call the '__karma__.loaded' function manually
var karmaLoadedFunction = window.__karma__.loaded;
window.__karma__.loaded = function () {};
Ext.onReady( function () {
console.info("Starting Tests ...");
window.__karma__.loaded = karmaLoadedFunction;
window.__karma__.loaded();
});
karma.conf.js:
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// Don't use Sencha Touch dynamic loading
files: [
'touch/sencha-touch-all-debug.js',
'spec/Setup.js', // Load stubbed app - does not call app.launch()
{ pattern: 'spec/**/*.js', watched: true, served: true, included: true },
{ pattern: 'app/**/*.js', watched: true, served: true, included: false},
// Some class are not loaded by sencha-touch-all-debug.js
// this tell Karma web server that it's ok to serve them.
{ pattern: 'touch/src/**/*.*', watched: false, served: true, included: false}
],
// // Use Sencha Touch static 'testing' app.js
// files: [
// './build/testing/PT/app.js',
// './spec/SetUp.js',
// './spec/**/*.js'
// ],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE/.LOG_ERROR/.LOG_WARN/.LOG_INFO/.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
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera (has to be installed with `npm install karma-opera-launcher`)
// - Safari (only Mac; has to be installed with `npm install
// karma-safari-launcher`)
// - PhantomJS
// - IE (only Windows; has to be installed with `npm install
// karma-ie-launcher`)
//browsers: [ 'PhantomJS' ],
browsers: ['Chrome'],
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 60000,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false
});
};
You need to spec/javascripts/support/jasmime.yml the files you need in the right order :
src_files:
- touch/sencha-touch-all-debug.js # Load Sencha library
- spec/app.js # Load our spec Ext.Application
- app/util/Urls.js #custom dependency
- app/**/*.js # Load source files
One way to get around the problem would be to define items from initComponent. That way it won't be called until instantiated, instead of at startup.
Ext.define('MyAppName.view.LoginView', {
extend: 'Ext.form.Panel',
alias: "widget.loginview",
config: {
title: 'Login'
},
initComponent: function() {
this.items = [
{
xtype: 'image',
src: Ext.Viewport.getOrientation() == 'portrait' ? '../../../img/login.png' : '../../../img/login-small.png',
style: Ext.Viewport.getOrientation() == 'portrait' ? 'width:80px;height:80px;margin:auto' : 'width:40px;height:40px;margin:auto'
}
];
this.callParent();
}
});
And the same thing for the store, but in the constructor
Ext.define('MyAppName.store.MyStore', {
extend: 'Ext.data.Store',
config: {
model: 'MyAppName.model.MyModel',
autoLoad: true
},
constructor: function(cfg) {
this.proxy = {
type: 'ajax',
url: MyAppName.app.backendUrl + '/data.json',
reader: 'json'
};
this.callParent(arguments)
}
});

Resources