Test Angular controller with Protractor - unit-testing

This is example of my code:
describe('myCtrl functionality', function() {
var driver;
var ptor;
beforeEach(function() {
ptor = protractor.getInstance();
ptor.ignoreSynchronization = true;
browser.ignoreSynchronization = false;
driver = ptor.driver;
});
it('should login', function() {
driver.get('someurl');
driver.findElement(protractor.By.name('username')).sendKeys('admin');
driver.findElement(protractor.By.name('password')).sendKeys('admin');
driver.findElement(protractor.By.css('button[type="submit"]')).click();
});
describe('myCtrl testing', function() {
var $scope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('dashStoresCtrl', {$scope: $scope});
}));
it('should create "stores" model', function() {
var containerStores = element(by.css('.dashboardStores'));
containerStores.findElements(by.css('.store-item-holder')).then(function(elems) {
expect(elems.length).toEqual($scope.stores.length);
});
});
});
});
And the problem is when i run tests i get TypeError: object is not a function.
That is for the line beforeEach(module('myApp'));
I made research and find out that i need to include angular-mocks.js file in my project and in index.html.
I did it but still get TypeError: object is not a function.
Anyone who can help with this?
Thanks!!!

Protractor tests are end-to-end tests, where NodeJS executes tests that connect to your browser and use it like a numan being would do.
You're trying, in such a protractor test, to use the angularJS API and modules to unit-test a controller. That doesn't make much sense.
Unit tests are typically executed by Karma, inside your browser, and end-to-end protractor tests are typically executed using protractor, inside NodeJS. You shouldn't have a unit test and a protractor test in the same file.

Related

Unit test Angularjs file upload controller method

i'm trying to unit test a controllers method in Angularjs that is responsible for file upload:
$scope.uploadFile = function() {
var fd = new FormData();
for (var i in $scope.files) {
fd.append("uploadedFile", $scope.files[i]);
}
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", $scope.uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.open("POST", "/fileupload");
xhr.send(fd);
}
i tried to mock the xhr object like the following :
it("using $window ", inject(function($window) {
$window.XMLHttpRequest= angular.noop;
addEventListenerSpy = jasmine.createSpy("addEventListener");
openSpy = jasmine.createSpy("open");
sendSpy = jasmine.createSpy("send");
xhrObj = {
upload:
{
addEventListener: addEventListenerSpy
},
addEventListener: addEventListenerSpy,
open: openSpy,
send: sendSpy
};
spyOn($window, "XMLHttpRequest").andReturn(xhrObj);
}));
when i run karma test config file i have the following error :
TypeError: Attempted to assign to readonly property.
at workFn (/home/dre/trunk/app/bower_components/angular-mocks/angular-mocks.js:2107)
can anyone help me i'm new in unit testing with jasmine and karma
You should be using Angular dependency injection which greatly helps tests. You should also use Angular's $http or $resource service to perform XHR requests.
Having said that, I created a fiddle with your test.
Test implementation are missing in your question, e.g. controller creation.
I hope the full example gives you a clue as to the problem in your code.
it("using $window ", function () {
xhrObj = jasmine.createSpyObj('xhrObj',
['addEventListener', 'open', 'send']);
spyOn(window, "XMLHttpRequest").andReturn(xhrObj);
scope.uploadFile()
expect(xhrObj.addEventListener).toHaveBeenCalled();
expect(xhrObj.addEventListener.calls.length).toBe(2);
});
You can find the full example here.
But I urge you to use $http/$resource instead.

Jasmine Tests in a Durandal App

Scenario
I am in the process of writing a number of jasmine tests for a Durandal based app that I am in the process of writing. The Durandal documentation suggests that the way to write tests is like
ViewModel
define([
'knockout',
'plugins/router',
'services/unitofwork',
'services/logger',
'services/errorhandler',
'services/config'
],
function (ko, router, unitofwork, logger, errorhandler, config) {
var uow = unitofwork.create();
var searchTerm = ko.observable();
var results = ko.observableArray([]);
var search = function () {
uow.myySearch(searchTerm).then(function (data) {
results(data);
logger.log(data.length + ' records found', '', 'myViewModel', true);
});
};
var vm = {
search : search,
searchTerm : searchTerm,
results : results
};
});
Test
define(['viewmodels/myViewModel'], function (myViewModel) {
describe('Stuff im testing', function(){
it('returns true', function () {
expect(true).toBe(true);
});
});
});
and for most of my tests this works great.
Problem
How do I mock/stub/fake a module that has been passed into ViewModel. For instance the UnitOfWork module so that it always returns a standard set of data.
For unit testing check out https://github.com/iammerrick/Squire.js/ a dependency mocker for requirejs. Another technique using require context is described in How can I mock dependencies for unit testing in RequireJS?.
For integration testing you might look into something like http://saucelabs.com (selenium based).
For some grunt tasks that helps setting up unit tests in phantomjs|browser see https://github.com/RainerAtSpirit/HTMLStarterKitPro (Disclaimer: I'm the maintainer of the repo). I'd love to see some mockup integration, so send a pull request if you feel inclined.
Check this out
https://github.com/danyg/jasmine-durandal
this is a library that I'm working on, in a few days will have the ability to test widgets too.

Angularjs - how to correctly replace service dependency with a mock

I'm using yeoman generator created app, and doing my tests in karma.
I have reusable mock objects for every of my service.
How do i correctly replace specific service dependcy with a mock, so i could then use jasmine to spy upon methods
So far i have done like this:
My service:
angular.module('ql')
.service('loginService', ['$http','API','authService', function ($http, API, authService) {
return {
//service implementation
}]);
Mock of authService:
'use strict';
//lets mock http auth service, so it would be spied upon.
ql.mock.$authServiceMockProvider = function() {
this.$get = function() {
var $service = {
loginConfirmed: function() { }
};
return $service;
};
};
//and register it.
angular.module('qlMock').provider({
$authServiceMock: ql.mock.$authServiceMockProvider
});
And my test:
'use strict';
describe('When i call login method()', function () {
// load the service's module
beforeEach(module('ql'));
beforeEach(angular.mock.module('qlMock'));
// instantiate service
var loginService,
authService,
$httpBackend;
beforeEach(function() {
// replace auth service with a mock.
// this seems kind of dirty... is there a bettery way?
module(function($provide, $injector){
authService = $injector.get('$authServiceMockProvider').$get();
$provide.value('authService', authService);
});
//actually get the loginService
/*jshint camelcase: false */
inject(function(_loginService_, _$httpBackend_) {
loginService = _loginService_;
$httpBackend =_$httpBackend_;
});
//http auth module method, that should be call only on success scenarios
spyOn(authService, 'loginConfirmed').andCallThrough();
});
it('it should do something', function () {
//actual test logic
});
});
What i do not like is the line:
authService = $injector.get('$authServiceMockProvider').$get();
I would like to simply somehow get the authServiceMock (without getting provider, and calling et method) and then inject it into loginService.
I know i could call my $authServiceMock simply authService, and provide it as a mock, so that it would always override my default implementation, but i do not want to do this.
I know this is late but maybe it will help someone who happen upon this post.
Mocking a service in Jasmine is quite simple using Angular's $provide service. The trick is to use $provide to swap out a service implementation before injecting the service.
For example let's say we are testing a service that makes use of the $location service to get information about the current URL.
// load the service's module under test
beforeEach(module('myExampleModule'));
// mock out $location with a fake one
beforeEach(module(function ($provide) {
//create mock impl
var mockLocation = {
path: function(){
return '/somewhere'
}
}
$provide.value('$location', mockLocation); // use $provide to swap the real $location with our mock
}));
var $location;
// inject dependencies ($location will be our mocked $location)
beforeEach(inject(function (_$location_) {
$location = _$location_;
}));
it('should return mock url', function(){
var path = $location.path();
expect(path).toBe('/somewhere'); //Assert that $location.path() returns '/somewhere'
});
I think I would simply use an angular service decorator to mock or totally replace your service for tests. Here is an example
I have never unit tested a service in a service, not yet anyways but our authertication/login stuff is coming up soon.
As you are unit testing the loginService you are only interested in the way the service interacts with the data it is given by the AuthService and not that the AuthService is working correctly. Which is what you have set up in the mock.
I think this would be my approach: (inside the parent describe)
var
loginService,
authService
AUTH_DATA
;
beforeEach(function() {
module('ql');
// I am assuming this is the global app module so both services live here? If not include this module as well
});
beforeEach(inject(function (_authService_, _loginService_) {
authService = _authService_;
loginService = _loginService_;
//Now with the spy setup you intercept the calls to the service and you choose what data to return, based on the unit test. Now your LoginService can simply resond to the data it is give from the login service
}));
it('it should do something', function () {
spyOn(authService, 'loginConfirmed').andReturn(AUTH_DATA);
loginService.confirmLogin(); //Dont know your actual API but a contrived guess
expect('something to happen in loginservice when AUTH_DATA is returned').toBe('Something else')
});

AngularJS Unit testing $location

I am pretty new at unit testing and AngularJS and I have some issue that I can't fix. One of my test is not working. I am trying to initiate a location.path() in my test by affecting a value, but in my controller, location.path() still have a undefined value.
Here is my controler:
angular.module('...')
.controller('SignUpCtrl', ['$location', function ($location) {
// Retrieve type of user
var userType = $location.path().substr(9);
if(userType == 'member'){
userType = 'user';
}
console.log($location.path());
console.log(userType);
$scope.uType = userType; ]);
And here is my test module:
describe('Controller: SignUpCtrl', function () {
// load the controller's module
beforeEach(module('...'));
var SignUpCtrl,
scope,
mockBackend,
environments,
location,
store;
beforeEach(inject(function ($controller, $rootScope, $httpBackend,$location,_Environments_) {
environments = _Environments_;
mockBackend = $httpBackend;
location = $location;
scope = $rootScope.$new();
SignUpCtrl = $controller('SignUpCtrl', {
$scope: scope,
$location: location
});
}));
it('should come from the right location', function(){
location.path('/sign-up/member');
expect(location.path()).toBe('/sign-up/member');
expect(scope.uType).toBe('user'); //Do not work
});
});
You're trying to use unit testing to do something that can only really be achieved using End-to-End (or E2E) testing. Unit testing in AngularJS is designed to test the javascript within a given module or sub-module (such as a service, factory, directive, etc). However, things like page navigation or browser location really need to be tested in an end-to-end testing environment.
Because of that, your $location object won't have all the normal methods (like path, url, etc). The $location object ends up simply being a "mock" of the actual $location object that you'd get in your module. So, you just need to move your test case for it('should come from the right location', function(){ ... }) to an end-to-end test and then continue on with your other module-specific unit tests. After you do that, you can simplify the $controller by only grabbing the $scope variable, as in the following:
scope = $rootScope.new();
SignUpCtrl = $controller('SignUpCtrl', {$scope: scope});
The guide for E2E testing can be found at this link. It walks you through how to write good E2E tests. There is a really great framework available for doing angular E2E tests called Protractor. The info for that is at this link. Protractor will soon (in 1.2) replace Karma as a better way to handle E2E testing.

Mocking AngularJS module dependencies in Jasmine unit tests

I'm attempting to unit test controller code inside a module that takes other modules as dependencies, but haven't been able to figure out how to mock them properly.
I'm using the Jasmine Framework and running my tests with Karma (Testacular).
Module Code
var app = angular.module('events', ['af.widgets', 'angular-table']);
app.controller('eventsCtrl', function([dependencies]){
$scope.events = [];
...
});
Spec Code
describe('events module', function(){
var $scope,
ctrl;
beforeEach(function(){
angular.mock.module('af.widgets', []);
angular.mock.module('angular-table', []);
module('events', ['af.widgets', 'angular-table']);
});
beforeEach(inject(function($rootScope, $controller){
$scope = $rootScope.new();
ctrl = $controller('NameCtrl', {
$scope: $scope,
});
}));
it('should have an empty events array', function(){
expect($scope.events).toBe([]);
})
});
The error I'm getting is Karma is "no module af.widgets", so obviously I'm not mocking the module dependencies right. Any hints?
If you want to mock a module that declare one or more services I have used this code:
beforeEach(function(){
module('moduleToMock');
module(function ($provide) {
$provide.value('yourService', serviceMock);
});
});
This is useful if the service you want to mock is also a service that you want to unit test (in another jasmine describe).
The solution proposed by fscof is fine but you cannot create a unit test for the angular-table module.
Here's what I figured out:
I wasn't loading any 'angular-table' modules in my karma.conf.js file, hence the error. This was intentional at first as I wanted to test the 'events' module without the actual table module.
I was able to easily mock the 'angular-table' module by creating a new file in my test folder called 'mocks/angular-table.js' and added the following code:
/mocks/angular-table.js
'use-strict';
angular.module('angular-table', []);
I added this file to my karma.conf.js file, along with the real 'events' module I wanted to test:
karma.conf.js
...
files = [
JASMINE,
JASMINE_ADAPTER,
'scripts/libs/angular.js',
'scripts/libs/angular-mocks.js',
'scripts/events.js', // this is the real module.
'scripts/mocks/*.js', //loads all custom mocks.
'scripts/specs/*.spec.js' // loads my spec file.
]
...
Finally in my spec file, I was able to add both modules by calling them separately in a beforeEach block:
specs/events.spec.js
beforeEach(function(){
module('angular-table');
module('events');
});
I got the idea to structure my files in this way from this post
I recently released ngImprovedTesting that should make mock testing in AngularJS way easier.
In your case just use the following in your Jasmine test:
beforeEach(ModuleBuilder.forModule('events').serviceWithMocks('eventsCtrl').build());
For more information about ngImprovedTesting check out its introductory blog post: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/