Mock with jasmine - angularjs

I want to isolate my angular unit specs.
My code base consists of a factory and a controller:
.factory('ServiceData', ->
{ message: 'im data from a factory service' }
)
.controller('Controller', ['$scope','ServiceData',($scope, ServiceData)->
$scope.serviceData = ServiceData
And here is a spec for the controller:
it 'should get data from a service', ->
spyOn(ServiceData, 'message').andReturn('im from a mock') # this doesn't work
expect($scope.serviceData).toEqual 'im from a mock'
The spec doesn't work, because I get the following error:
ReferenceError: ServiceData is not defined in ...controller_spec.js
I want to isolate my controller spec from my factory. How do I achieve this?
Also, this is a trivial example, but in future I will have very complex factories that get data from the server and all sorts. So I want to know how to stub them out.
I also might want to test how my controller behaves based on different things that are returned by the mocked out function, so I definitely want to use this approach.
I've tried to using $provide, but to much success
beforeEach ->
module 'App', ($provide)->
$provide.value 'serviceData', { message: 'im from a mock' }
results in:
Error: [ng:areq] Argument 'fn' is not a function, got Object
I mean...I just don't even know

Quote:
it 'should get data from a service', ->
spyOn(ServiceData, 'message').andReturn('im from a mock') # this doesn't work
expect($scope.serviceData).toEqual 'im from a mock'
The spec doesn't work, because I get the following error:
ReferenceError: ServiceData is not defined in ...controller_spec.js
/quote
I think it should be something more like this:
it 'should get data from a service', -> //this is coffeescript syntax you're using I assume
$scope.serviceData = ServiceData.serviceData
expect($scope.serviceData).toEqual 'im from a service'
//where ServiceData is defined as a service that returns a Plain Old Javascript Object
//and that service returns an object in format: { serviceData: 'im from a service' }
If I understand what you are trying to test

Related

How can I obfuscate AngularJS codes?

How can I obfuscate AngularJS codes?
We have tried gulp-obfuscate, but it does not work. Anyone can help us? Thanks a lot!
We have done like this
someModule.controller('MyController',[ '$scope', function($scope) {",
but after success to get the obfuscated codes like this
function H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅4() {
var H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅1, H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅2, H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅3;
...
H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅3 = H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅1 + H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅2;
return H͇̬͔̳̖̅̒ͥͧẸ̖͇͈͍̱̭̌͂͆͊_C͈OM̱̈́͛̈ͩ͐͊ͦEͨ̓̐S̬̘͍͕͔͊̆̑̈́̅3;
}
all angularjs codes in app-52143d391a.js not worked, it return
Uncaught Error: [$injector:nomod] Module 'client' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
After run
gulp.task('obfuscate', function () {
return gulp.src('../webapp/scripts/app-*.js')
.pipe(ngAnnotate())
.pipe(obfuscate())
.pipe(gulp.dest('dist'));
});
all are ok, and get below codes:
http://pan.baidu.com/s/1ntXuGbB
then run with below error:
Uncaught TypeError:  _ 206. _ 252 is not a function(anonymous function) # app-6c38d9a2fc.js:2
generated.js:9279Uncaught Error: [$injector:modulerr] Failed to instantiate module client due to:
Error: [$injector:nomod] Module 'client' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.4.7/$injector/nomod?p0=client
From the ngDocs:
Careful: If you plan to minify your code, your service names will get
renamed and break your app.
To make your code work after minification you have to use the inject property, every-time you use dependancy injection, to annotate your components. So even if the variable names change angular will know how to map the mangled variables with the proper service.
e.g:
Here we pass the $scope with dependency injection:
someModule.controller('MyController', function($scope) {
For this line to work you have to change to this:
someModule.controller('MyController',[ '$scope', function($scope) {
Or:
MyController.$inject = ['$scope'];
someModule.controller('MyController', function($scope) {
Or even better.
Use gulp-ng-annotate before the obsfucate task which does all that work for you.
UPDATE
After Obsfucation you should see the $inject string as they where, instead this plugin obsufucates them also. Meaning:
['$http', function($http) {
becomes:
[ "ಠ_ಠ727", function( ಠ_ಠ727 )
instead of:
[ "$http", function( ಠ_ಠ727 )
There is a relevant issue on github
I would suggest you use another plugin. With gulp-uglify I didn't have any issue.

Can't get this unit test (on a helper) to run without exceptions - AngularJS, Karma, Jasmine

I'm new to Angular and Karma and every site out there seems to recommend a different way of writing unit tests, which makes all of this very confusing. Help is appreciated!
I have a helper class that has a dependency on a service class. I am writing a unit test for the helper class. I have this:
module("myModule");
it('works!', inject(function(myHelper) {
module(function($provide) {
$provide.service('myService', function() {
payload = spyOn(myService, 'getPayload').andReturn(
{id: 1 });
});
});
expect(myHelper.getSomeData()).toEqual(exepectedData);
}));
The exception I'm getting when running the test is:
Error: [$injector:unpr] Unknown provider: myHelperProvider <- myHelper
I've tried all different ways of doing this, but haven't gotten it to work yet.
Try calling module("myModule"); in a beforeEach, i.e.:
beforeEach(module('myModule'));
You may have luck with actually calling the function returned by module("myModule"), i.e.:
module("myModule")();
... but have never tried this and I have serious doubts.
Also I always enclose my it() specs in a describe() block; not sure if strictly necessary though.

AngularJS - obtaining an instance of an injected module/service

In this post #yair-tavor posted snippets related to his SOAP interceptor module. Unfortunately, there is no clear and obvious example provided for obtaining an instance of the created 'myModule' object. I've already downloaded Yair's code from the Fiddle provided in that post, and included it in the HTML web page I'm developing. Being completely new to the AngularJS world (and learning more every minute!) I'd like to ask precisely HOW to obtain a local instance of that module/service.
I see that Yair's code includes this:
/**
* To be used by angular, this method retrieves new
* {{#crossLink "rtv.data.soap-interceptor"}}{{/crossLink}} instance.
* #method $get
* #returns {SoapInterceptor}
* #for myModule.soap-interceptorProvider
*/
providerInstance.$get = function(){
return new SoapInterceptor();
};
}]);
I've bookmarked this very helpful SO post for some detailed review, tomorrow:
AngularJS: Service vs provider vs factory
So, I've tried to use the following syntax to obtain an instance, but it appears to return a module full of null objects, rather than the module I thought I was loading. :( Attempting to call soap.setWSDL using this module returns "TypeError: undefined is not a function".
var soapModule = angular.module('myModule.soap-interceptor', [] );
soapModule: {
_invokeQueue:
[ ]
_runBlocks:
[ ]
requires:
[ ]
name: soapModule.soap-interceptor
provider: null
factory: null
service: null
value: null
constant: null
animation: null
filter: null
controller: null
directive: null
config: null
run: null
}
Nor does calling it without the second argument, which returns an error saying the module is not available.
var soapModule = angular.module('myModule.soap-interceptor');
Error: [$injector:nomod] Module 'myModule.soap-interceptor' is not available!
You either misspelled the module name or forgot to load it. If registering a
module ensure that you specify the dependencies as the second argument.
So, how exactly SHOULD I be loading and hooking into Yair's soap-interceptor module? I need a valid non-null instance of it, in order to successfully execute the "setWSDL" method which loads the WSDL content.

Getting error when trying to use $provide in jasmine unit test

I'm using AngularJS and trying to test a controller that calls a factory to get some data.
This is the controller code:
'use strict'
angular.module('AngularApp')
.controller 'IndexCtrl', ($scope, session, navigation) ->
session.find().then (response) ->
$scope.session = response.data
$scope.someOtherVariable = {}
Naturally, I'd like to swap the factory with a mock to prevent calling a real API. I'm trying to use $provide.factory to inject the mock copy:
'use strict'
describe 'Controller: IndexCtrl', ->
# load the controller's module
beforeEach module 'mosaicAdminWebClientApp'
beforeEach module ($provide) ->
$provide.factory 'session', ->
true
IndexCtrl = {}
scope = {}
# Initialize the controller and a mock scope
beforeEach inject ($controller, $rootScope) ->
scope = $rootScope.$new()
IndexCtrl = $controller 'IndexCtrl', {
$scope: scope
}
it 'should attach a list of awesomeThings to the scope', ->
expect(true).toBe true
When running this test with Karma, I guess this error:
Chrome 32.0.1700 (Mac OS X 10.9.1) Controller: IndexCtrl should attach a list of awesomeThings to the scope FAILED
Error: [ng:areq] Argument 'fn' is not a function, got Object
http://errors.angularjs.org/1.2.10-build.2176+sha.e020916/ng/areq?p0=fn&p1=not%20a%20function%2C%20got%20Object
at /Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:78:12
at assertArg (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:1363:11)
at assertArgFn (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:1373:3)
at annotate (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3019:5)
at Object.invoke (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3685:21)
at /Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3554:71
at Array.forEach (native)
at forEach (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:303:11)
at Object.createInjector [as injector] (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3554:3)
at workFn (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular-mocks/angular-mocks.js:2144:52)
Chrome 32.0.1700 (Mac OS X 10.9.1): Executed 10 of 10 (1 FAILED) (0.26 secs / 0.068 secs)
Warning: Task "karma:unit" failed. Use --force to continue.
Aborted due to warnings.
I've tried many different permutation, such as removing the label, passing an object or simple value instead of a function and none of them worked.
The documentation (http://docs.angularjs.org/api/AUTO.$provide) shows that I should call the factory() method with as factory(name, $getFn) where name is a string and $getFn is a function. That's what I'm doing but it's not working.
Anything I've missed? Does anyone know how to properly use $provide in Jasmine unit tests?
Thanks
Update:
I found a plunkr similar that solved an issue similar to this one here: stackoverflow.com/questions/19297258/why-is-provide-only-available-in-the-angular-mock-module-function-and-q-onl
I created my own plunkr with this code, but with the test code in JS instead of Coffee and got it to work: plnkr.co/edit/gfBzMXpKdJgPKnoyJy5A?p=preview
Now I manually converted the JS code into Coffee: plnkr.co/edit/qGGMayFjJoeYZyFKPjuR?p=preview
The error is back so the coffee code is wrong but the js code works.
Found the answer to my question.
Since CoffeeScript always returns the result from the last statement, in this case, CoffeeScript returns $provide inside of module(), which is wrong. An easy way to fix that is to just manually add a return statement after $provide, like this:
beforeEach module 'mosaicAdminWebClientApp', ($provide) ->
$provide.service 'session', ()->
return
Hope that will help someone else.

Loading a mock JSON file within Karma+AngularJS test

I have an AngularJS app set up with tests using Karma+Jasmine. I have a function I want to test that takes a large JSON object, converts it to a format that's more consumable by the rest of the app, and returns that converted object. That's it.
For my tests, I'd like you have separate JSON files (*.json) with mock JSON content only--no script. For the test, I'd like to be able to load the JSON file in and pump the object into the function I'm testing.
I know I can embed the JSON within a mock factory as described here: http://dailyjs.com/2013/05/16/angularjs-5/ but I really want the JSON to not be contained within script--just straight JSON files.
I've tried a few things but I'm fairly noob in this area. First, I set up my Karma to include my JSON file just to see what it would do:
files = [
...
'mock-data/**/*.json'
...
]
This resulted in:
Chrome 27.0 (Mac) ERROR
Uncaught SyntaxError: Unexpected token :
at /Users/aaron/p4workspace4/depot/sitecatalyst/branches/anomaly_detection/client/anomaly-detection/mock-data/two-metrics-with-anomalies.json:2
So then I changed it to just serve the files and not "include" them:
files = [
...
{ pattern: 'mock-data/**/*.json', included: false }
...
]
Now that they're only served, I thought I'd try to load in the file using $http from within my spec:
$http('mock-data/two-metrics-with-anomalies.json')
When I ran the spec I received:
Error: Unexpected request: GET mock-data/two-metrics-with-anomalies.json
Which in my understanding means it expects a mocked response from $httpBackend. So...at this point I didn't know how to load the file using Angular utilities so I thought I'd try jQuery to see if I could at least get that to work:
$.getJSON('mock-data/two-metrics-with-anomalies.json').done(function(data) {
console.log(data);
}).fail(function(response) {
console.log(response);
});
This results in:
Chrome 27.0 (Mac) LOG: { readyState: 4,
responseText: 'NOT FOUND',
status: 404,
statusText: 'Not Found' }
I inspect this request in Charles and it's making a request to
/mock-data/two-metrics-with-anomalies.json
Whereas the rest of the files I've configured to be "included" by Karma are being requested at, for example:
/base/src/app.js
Apparently Karma's setting up some sort of base directory to serve the files from. So for kicks I changed my jquery data request to
$.getJSON('base/mock-data/two-metrics-with-anomalies.json')...
And it works! But now I feel dirty and need to take a shower. Help me feel clean again.
I'm using an angular setup with angular seed. I ended up solving this with straight .json fixture files and jasmine-jquery.js. Others had alluded to this answer, but it took me a while to get all the pieces in the right place. I hope this helps someone else.
I have my json files in a folder /test/mock and my webapp is in /app.
my karma.conf.js has these entries (among others):
basePath: '../',
files: [
...
'test/vendor/jasmine-jquery.js',
'test/unit/**/*.js',
// fixtures
{pattern: 'test/mock/*.json', watched: true, served: true, included: false}
],
then my test file has:
describe('JobsCtrl', function(){
var $httpBackend, createController, scope;
beforeEach(inject(function ($injector, $rootScope, $controller) {
$httpBackend = $injector.get('$httpBackend');
jasmine.getJSONFixtures().fixturesPath='base/test/mock';
$httpBackend.whenGET('http://blahblahurl/resultset/').respond(
getJSONFixture('test_resultset_list.json')
);
scope = $rootScope.$new();
$controller('JobsCtrl', {'$scope': scope});
}));
it('should have some resultsets', function() {
$httpBackend.flush();
expect(scope.result_sets.length).toBe(59);
});
});
The real trick was the jasmine.getJSONFixtures().fixturesPath='base/test/mock';
I had originally set it to just test/mock but it needed the base in there.
Without the base, I got errors like this:
Error: JSONFixture could not be loaded: /test/mock/test_resultset_list.json (status: error, message: undefined)
at /Users/camd/gitspace/treeherder-ui/webapp/test/vendor/jasmine-jquery.js:295
Serving JSON via the fixture is the easiest but because of our setup we couldn't do that easily so I wrote an alternative helper function:
Repository
Install
$ bower install karma-read-json --save
OR
$ npm install karma-read-json --save-dev
OR
$ yarn add karma-read-json --dev
Usage
Put karma-read-json.js in your Karma files. Example:
files = [
...
'bower_components/karma-read-json/karma-read-json.js',
...
]
Make sure your JSON is being served by Karma. Example:
files = [
...
{pattern: 'json/**/*.json', included: false},
...
]
Use the readJSON function in your tests. Example:
var valid_respond = readJSON('json/foobar.json');
$httpBackend.whenGET(/.*/).respond(valid_respond);
I've been struggling to find a solution to loading external data into my testcases.
The above link: http://dailyjs.com/2013/05/16/angularjs-5/
Worked for me.
Some notes:
"defaultJSON" needs to be used as the key in your mock data file, this is fine, as you can just refer to defaultJSON.
mockedDashboardJSON.js:
'use strict'
angular.module('mockedDashboardJSON',[])
.value('defaultJSON',{
fakeData1:{'really':'fake2'},
fakeData2:{'history':'faked'}
});
Then in your test file:
beforeEach(module('yourApp','mockedDashboardJSON'));
var YourControlNameCtrl, scope, $httpBackend, mockedDashboardJSON;
beforeEach(function(_$httpBackend_,defaultJSON){
$httpBackend.when('GET','yourAPI/call/here').respond(defaultJSON.fakeData1);
//Your controller setup
....
});
it('should test my fake stuff',function(){
$httpBackend.flush();
//your test expectation stuff here
....
}
looks like your solution is the right one but there are 2 things i don't like about it:
it uses jasmine
it requires new learning curve
i just ran into this problem and had to resolve it quickly as i had no time left for the deadline, and i did the following
my json resource was huge, and i couldn't copy paste it into the test so i had to keep it a separate file - but i decided to keep it as javascript rather than json, and then i simply did:
var someUniqueName = ... the json ...
and i included this into karma conf includes..
i can still mock a backend http response if needed with it.
$httpBackend.whenGET('/some/path').respond(someUniqueName);
i could also write a new angular module to be included here and then change the json resource to be something like
angular.module('hugeJsonResource', []).constant('SomeUniqueName', ... the json ... );
and then simply inject SomeUniqueName into the test, which looks cleaner.
perhaps even wrap it in a service
angular.module('allTestResources',[]).service('AllTestResources', function AllTestResources( SomeUniqueName , SomeOtherUniqueName, ... ){
this.resource1 = SomeUniqueName;
this.resource2 = SomeOtherUniqueName;
})
this solutions was faster to me, just as clean, and did not require any new learning curve. so i prefer this one.
I was looking for the same thing. I'm going to try this approach. It uses the config files to include the mock data files, but the files are a little more than json, because the json needs to be passed to angular.module('MockDataModule').value and then your unit tests can also load multiple modules and then the value set is available to be injected into the beforeEach inject call.
Also found another approach that looks promising for xhr requests that aren't costly, it's a great post that describes midway testing, which if I understand right lets your controller/service actually retrieve data like in an e2e test, but your midway test has actual access to the controller scope (e2e doesn't I think).
There are Karma preprocessors that work with JSON files also. There is one here:
https://www.npmjs.org/package/karma-ng-json2js-preprocessor
And shameless plug, this is one I developed that has RequireJS support
https://www.npmjs.org/package/karma-ng-json2js-preprocessor-requirejs
You can use the karma-html2js-preprocessor to get the json files added to the __html__ global.
see this answer for details: https://stackoverflow.com/a/22103160/439021
Here is an alternative to Cameron's answer, without the need for jasmine-jquery nor any extra Karma config, to test e.g. an Angular service using $resource :
angular.module('myApp').factory('MyService', function ($resource) {
var Service = $resource('/:user/resultset');
return {
getResultSet: function (user) {
return Service.get({user: user}).$promise;
}
};
});
And the corresponding unit test :
describe('MyServiceTest', function(){
var $httpBackend, MyService, testResultSet, otherTestData ;
beforeEach(function (done) {
module('myApp');
inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
MyService = $injector.get('MyService');
});
// Loading fixtures
$.when(
$.getJSON('base/test/mock/test_resultset.json', function (data) { testResultSet = data; }),
$.getJSON('base/test/mock/test_other_data.json', function (data) { otherTestData = data; })
).then(done);
});
it('should have some resultset', function() {
$httpBackend.expectGET('/blahblahurl/resultset').respond(testResultSet);
MyService.getResultSet('blahblahurl').then(function (resultSet) {
expect(resultSet.length).toBe(59);
});
$httpBackend.flush();
});
});

Resources