I have a REST client written to be part of an angularJS application that I want to write tests for; I tried with Jasmine (using passthrough on the $httpBackend) but could not get it to talk to the real endpoint at all (which is a requirement).
Does anybody know of a sensible library that allows for this? Or alternatively, a way of wrestling Jasmine into submission?
you need to inject first $httpbackend
describe('MyController', function() {
var $httpBackend, $rootScope, createController, authRequestHandler;
// Set up the module
beforeEach(module('MyApp'));
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
authRequestHandler = $httpBackend.when('GET', '/auth.py')
.respond({userId: 'userX'}, {'A-Token': 'xxx'});
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
return $controller('MyController', {'$scope' : $rootScope });
};
$httpBackend.when('GET', "/api/rest/").respond(data_to_respond);
}));
next write test cases
it('getTypes - should return 3 car manufacturers', function () {
service.getTypes().then(function(response) {
//expect will be here
});
$httpBackend.flush();
});
Related
I have the following Jasmine unit test:
describe('myService', function () {
var myService, $q;
// Instantiate the app
beforeEach(module('myApp'));
beforeEach(inject(function (_myService_, fileSystemService, $q) {
myService = _myService_;
spyOn(fileSystemService, 'listFiles').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve('mockresult');
return deferred.promise;
});
}));
it('checks the number of outbound files', inject(function ($rootScope) {
var result;
myService.sendOutboundFiles2().then(function (res) {
result = res;
});
$rootScope.$digest();
expect(result).toBe('mockresult');
}));
});
Which tests this very simple service function:
sendOutboundFiles2() {
return fileSystemService.listFiles('Cached/Outbound').then(function(outfiles) {
return outfiles;
})
}
However when the test runs, it fails with a spurious Error: Unexpected request: GET blah\blah\blah.html No more request expected at $httpBackend error but i have no idea why as neither this test nor the service dependencies do anything with $httpBackend.
MORE INFO
If i comment out my existing controller tests, I get this error:
If i add my controller tests back in, I get this error:
So depending on which tests i add or remove, the HTML file in the GET error changes. But all the controller tests run fine. WTF?!?!?!!??!?!!?
The problem is caused by Ionic's keen prefetching of all templates into a cache. No idea why this doesn't occur when testing a controller though. The problem only appears when i was testing a service. Any way, I found this thread: Karma test breaks after using ui-router and the relevant fix is to add this snippets before injecting any dependencies:
beforeEach(module(function($provide) {
$provide.value('$ionicTemplateCache', function(){} );
}));
This stubs out the $ionicTemplateCache and prevents it from trying to preload all ui-router templates into the Ionic cache.
I am still very new to unit testing, and to be honest, there isn't anything that I could even think of testing, but I cannot build my app unless I have at least 1 test case, so I attempted to make the most simple test case I could, on the smallest block of code in the controller, and it doesn't seem to be working.
I believe it's an error in my test case, and not in my controller's code itself, because when I view my app in the browser with grunt serve the console shows no errors.
This is the error it gives me:
PhantomJS 2.1.1 (Linux 0.0.0) Controller: MainCtrl should attach a list of jackpot to the scope FAILED
/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3746:53
forEach#[native code]
forEach#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:323:18
loadModules#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3711:12
createInjector#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3651:22
workFn#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular-mocks/angular-mocks.js:2138:60
TypeError: undefined is not an object (evaluating 'scope.jackpot') in /home/elli0t/Documents/Yeoman Projects/monopoly/test/spec/controllers/main.js (line 20)
/home/elli0t/Documents/Yeoman Projects/monopoly/test/spec/controllers/main.js:20:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.04 secs / 0.007 secs)
This is my test case:
it('should attach a list of jackpot to the scope', function () {
expect(scope.jackpot.length).toBe(2);
});
And this is the block of code I'm attempting to run the test on:
var countInJackpot = localStorageService.get('jackpot');
$scope.jackpot = countInJackpot || [
{
letter: '$',
prize: '$1,000,000 Cash',
numbers: ['$611A','$612B','$613C','$614D','$615E','$616F','$617G','$618F'],
count: [0,0,0,0,0,0,0,0]
},
{
letter: '?',
prize: '$500,000 Vacation Home',
numbers: ['?619A','?620B','?621C','?622D','?632E','?624F','?625G','?626H'],
count: [0,0,0,0,0,0,0,0]
}
];
For the time being, I really just want to write 1 simple test case, so it will let me build the app. I'm currently studying unit testing, but I still don't feel ready to write more complex test cases on my own. I will save that for later.
I have included the entire contents of the files in a gist for reference, if needed, and I can include the contents of the karma.conf.js if necessary.
My gist
Within your test case, scope should be $scope?
OR
You probably haven't setup your testing environment to load in your controller.
Here is an example of mine on testing a controller... Angular makes the setup a little iffy to learn, But once you understand the flow. It's pretty great :)
I'm going to try and add as many comments to explain each piece as I can... but let me know if your need clarification. You might be using jasmine, but keep in mind, this is mocha, im using the angular mock library loaded in via the karma.conf.
describe('myController', function() {
var $scope,
createController;
// Runs before each test. Re-extantiating the controller we want to test.
beforeEach(inject(function($injector) {
// Get hold of a scope (i.e. the root scope)
$scope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
// Creates the controller instance of our controller.
// We are injecting $scope so we will have access to it
// after the controllers code runs
return $controller('myCtrl', {
'$scope': $scope
});
};
}));
describe('#myFunction', function() {
it('jackpot should contain two objects', function() {
expect($scope.jackpot.length).to.equal(2);
});
});
});
Hope that helped. Here's some of the resources I used to learn :) Good Luck!
https://quickleft.com/blog/angularjs-unit-testing-for-real-though/
http://jaketrent.com/post/run-single-mocha-test/
I would expect you'd want to test both cases of the localStorageService having and not having data. To do so, create a spy for localStorageService (see Spies) and write your tests like this...
'use strict';
describe('Controller: MainCtrl', function () {
var scope, localStorageService, localData;
beforeEach(function() {
localData = {};
module('monopolyApp');
localStorageService = jasmine.createSpyObj('localStorageService', ['get', 'set']);
localStorageService.get.and.callFake(function(key) {
return localData[key];
});
inject(function($rootScope) {
scope = $rootScope.$new();
});
});
it('assigns jackpots from local storage if present', inject(function($controller) {
localData.jackpot = 'whatever, does not matter';
$controller('MainCtrl', {
$scope: scope,
localStorageService: localStorageService
});
expect(localStorageService.get).toHaveBeenCalledWith('jackpot');
expect(scope.jackpot).toBe(localData.jackpot);
}));
it('assigns jackpots from default array if none present in local storage', inject(function($controller) {
$controller('MainCtrl', {
$scope: scope,
localStorageService: localStorageService
});
expect(localStorageService.get).toHaveBeenCalledWith('jackpot');
expect(scope.jackpot.length).toEqual(2);
// maybe include some other checks like
expect(scope.jackpot[0].letter).toEqual('$');
expect(scope.jackpot[1].letter).toEqual('?');
}));
});
I am trying to test a http request with dynamic url
I have something in my service file like
My service file.
//other service codes..
//other service codes..
var id = $cookies.id;
return $http.get('/api/product/' + id + '/description')
//id is dynamic
Test file
describe('test', function () {
beforeEach(module('myApp'));
var $httpBackend, testCtrl, scope;
beforeEach(inject(function (_$controller_, _$httpBackend_, _$rootScope_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
}));
it('should check the request', function() {
$httpBackend.expectGET('/api/product/12345/description').respond({name:'test'});
$httpBackend.flush();
expect(scope.product).toBeDefined();
})
});
I am getting an error saying
Error: Unexpected request: GET /api/product/description
I am not sure how to test the dynamic url. Can anyone help me about it? Thanks a lot!
You don't have id set in your code, so the url becomes:
/api/product//description
Which is reduced to what you see in the unexpected request (// -> /)
So, why isn't id defined? Show the code where you set it.
In testing 'dynamic' urls, you need to set up your test so that you know what the value of id is, and expect that. There isn't a way to expect patterns of urls.
You can modify the value of cookies by changing the first line of your describe block:
beforeEach(module('myApp', function($provide) {
$provide.value('$cookies', {
id: 3
});
}));
Now you can expect that id will be three when the URL call happens.
This is pretty crude though. You could also just inject $cookies in the second beforeEach block and set it
beforeEach(inject(function($cookies) {
$cookies.put('id', 3);
}))
This question already has an answer here:
What does the underscores in _servicename_ mean in AngularJS tests?
(1 answer)
Closed 8 years ago.
I'm going through the AngularJS tutorial step 5, and came across this snippet in the testing section:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
// Load our app module definition before each test.
beforeEach(module('phonecatApp'));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));
I don't fully understand the purposes of the underscore when injecting $httpBackend. I see the comment and understand what the code is doing. I just don't get why we are only doing this with $httpBackend.
There are two other services we are injecting right along with it that don't need to be injected this way. How are we helping ourselves by injecting $httpBackend in a roundabout manner then immediately assigning it to a variable of the same name, couldn't we just inject it directly?
Because that way you can declare $httpBackend in your describe, assign the injected service to it in a beforeEach and use it in your it blocks.
example:
describe('PhoneCat controllers', function() {
var $httpBackend; // variable declaration
beforeEach(inject(function(_$httpBackend_) {
$httpBackend = _$httpBackend_; // assignment
}));
it('should do something', function(){
// usage
$httpBackend.expectGET('/myurl').respond(function(){
// some behaviour
});
// some assertion
});
});
As you must know now, angular $injector simply ignores those underscores, so for injection purposes $httpBackend and $httpBackend are the same. The current implementation simply replaces those underscores.
The only "advantage" is that if you use underscores in your parameters, for instances, $httpBackend you are free to use a global test variable with the name $httpBackend, otherwise you have to give your variables other name
var $httpBackend;
beforeEach(inject(function(_$httpBackend_) {
$httpBackend = _$httpBackend_;
}));
I'm testing service A, but service A depends on service B (i.e. service B is injected into service A).
I've seen this question but my case is a bit different because in my opinion it makes more sense to mock service B instead of injecting an actual instance of service B. I'd mock it with a jasmine spy.
Here's a sample test:
describe("Sample Test Suite", function() {
beforeEach(function() {
module('moduleThatContainsServiceA');
inject([
'serviceA', function(service) {
this.service = service;
}
]);
});
it('can create an instance of the service', function() {
expect(this.service).toBeDefined();
});
});
The error I get is:
Error: Unknown provider: serviceBProvider
How could I do something like this?
Actually in AngularJS Dependency Injection uses the 'last wins' rule. So you can define your service in your test just after including your module and dependencies, and then when service A that you're testing will request service B using DI, AngularJS will give mocked version of service B.
This is often is done by defining new module like MyAppMocks, putting mocked services/values there and then just adding this module as dependency.
Kind of (schematically):
beforeEach(function() {
angular.module('MyAppMocks',[]).service('B', ...));
angular.module('Test',['MyApp','MyAppMocks']);
...
I was doing this in CoffeeScript and found an extra gotcha. (Also, I found the code on this page to be confusingly terse.) Here's a complete working example:
describe 'serviceA', ->
mockServiceB = {}
beforeEach module 'myApp' # (or just 'myApp.services')
beforeEach ->
angular.mock.module ($provide) ->
$provide.value 'serviceB', mockServiceB
null
serviceA = null
beforeEach inject ($injector) ->
serviceA = $injector.get 'serviceA'
it 'should work', ->
expect( true ).toBe( true )
#serviceA.doStuff()
Without explicitly returning null after $provide.value, I kept getting Error: Argument 'fn' is not a function, got Object. I found the answer in this Google Groups thread.
The Valentyn solution worked for me, but there is another alternative.
beforeEach(function () {
angular.mock.module("moduleThatContainsServiceA", function ($provide) {
$provide.value('B', ...);
});
});
Then when AngularJS service A request the Service B by Dependency Injection, your mock of Service B will be provided instead of the Service B from moduleThatContainsServiceA.
This way you don't need to create an additional angular module just to mock a Service.
I find the simplest method is just to inject service B and mock it. e.g. Service car depends on service Engine. Now we need to mock Engine when testing Car:
describe('Testing a car', function() {
var testEngine;
beforeEach(module('plunker'));
beforeEach(inject(function(engine){
testEngine = engine;
}));
it('should drive slow with a slow engine', inject(function(car) {
spyOn(testEngine, 'speed').andReturn('slow');
expect(car.drive()).toEqual('Driving: slow');
}));
});
Reference: https://github.com/angular/angular.js/issues/1635
This is what worked for me. The key is defining a real module to be mocked. Calling angular.mock.module makes the real module mockable and allows things to be connected.
beforeEach( ->
#weather_service_url = '/weather_service_url'
#weather_provider_url = '/weather_provider_url'
#weather_provider_image = "test.jpeg"
#http_ret = 'http_works'
module = angular.module('mockModule',[])
module.value('weather_service_url', #weather_service_url)
module.value('weather_provider_url', #weather_provider_url)
module.value('weather_provider_image', #weather_provider_image)
module.service('weather_bug_service', services.WeatherBugService)
angular.mock.module('mockModule')
inject( ($httpBackend,weather_bug_service) =>
#$httpBackend = $httpBackend
#$httpBackend.when('GET', #weather_service_url).respond(#http_ret)
#subject = weather_bug_service
)
)