Unit test failing when function called on startup - javascript

This is an extension off a former question of mine: $httpBackend in AngularJs Jasmine unit test
In my controller, the getStuff function is called on startup. This causes my unit test to fail. When I comment it out, my unit tests pass and work successfully. When uncommented, the error is:
Error: Unexpected request: GET /api/stuff
No more request expected
My controller is:
$scope.stuff = [];
$scope.getStuff = function () {
var url = site.root + 'api/stuff';
$http.get(url)
.success(function (data) {
$scope.stuff = data;
})
.error(function(error) {
console.log(error);
});
};
//$scope.getStuff();
and my unit test is:
it('should get stuff', function () {
var url = '/api/stuff';
var httpResponse = [{ "stuffId": 1 }, { "stuffId": 2 }];
httpLocalBackend.expectGET(url).respond(200, httpResponse);
$scope.getStuff();
httpLocalBackend.flush();
expect($scope.stuff.length).toBe(2);
} );
Everything unit test wise, works fine like this. Unfortunately, this breaks the actual site functionality. When I uncomment the last line of the controller, the unit test breaks, and the site works. Any help is appreciated. Thanks!
FIXED: Thanks to fiskers7's answer, this is my solution.
it('should get stuff', function () {
var url = '/api/stuff';
var httpResponse = [{ "stuffId": 1 }, { "stuffId": 2 }];
httpLocalBackend.expectGET(url).respond(200, httpResponse);
httpLocalBackend.expectGET(url).respond(200, httpResponse);
$scope.getStuff();
httpLocalBackend.flush();
expect($scope.stuff.length).toBe(2);
} );

Verbatim from my comment:
When you create the controller it makes a call to 'api/stuff' and when you call $scope.getStuff() in your test you call it again. So instead of one call to 'api/stuff' you have two which is what the error is saying. httpBackend didn't expect two calls to the endpoint, only one so it throws the error.
Code example from my comment to this answer if you need to see it.
it('should get stuff', function () {
var url = '/api/stuff';
var httpResponse = [{ "stuffId": 1 }, { "stuffId": 2 }];
httpLocalBackend.expectGET(url).respond(200, httpResponse);
httpLocalBackend.flush();
expect($scope.stuff.length).toBe(2);
});

Related

Unit testing Angular HTTP interceptor

I have a standard HTTP interceptor as a factory:
angular
.module('app.services')
.factory('HttpInterceptorService', HttpInterceptorService);
function HttpInterceptorService($injector) {
// Callable functions
var service = {
response: response,
responseError: responseError
};
return service;
// Pass through clean response
function response(data) {
return data;
}
// Handle error response
function responseError(rejection) {
// Handle bypass requests
if (angular.isDefined(rejection.config) && rejection.config.bypassInterceptor) {
return rejection;
}
// Get $state via $injector to avoid a circular dependency
var state = $injector.get('$state');
switch (rejection.status) {
case 404:
return state.go('404');
break;
default:
return state.go('error');
}
}
}
In manual testing, I can see this works correctly by redirecting the user to the relevant 404 or error page if an HTTP call returns an error response. The basic principal of this is documented by Angular here: https://docs.angularjs.org/api/ng/service/$http#interceptors
Now I'm trying to write a unit test with Karma & Jasmine to test that the responseError function works correctly. I've checked out this SO answer to help me. My test looks like this:
describe('HttpInterceptorService', function() {
// Bindable members
var $window,
HttpInterceptorService;
// Load module
beforeEach(module('app.services'));
// Set window value
beforeEach(function () {
$window = { location: { href: null } };
module(function($provide) {
$provide.value('$window', $window);
});
});
// Bind references to global variables
beforeEach(inject(function(_HttpInterceptorService_) {
HttpInterceptorService = _HttpInterceptorService_;
}));
// Check service exists with methods
it('Exists with required methods', function() {
expect(HttpInterceptorService).toBeDefined();
expect(angular.isFunction(HttpInterceptorService.response)).toBe(true);
expect(angular.isFunction(HttpInterceptorService.responseError)).toBe(true);
});
// Test 404 HTTP response
describe('When HTTP response 404', function () {
beforeEach(function() {
HttpInterceptorService.responseError({ status: 404 });
});
it('Sets window location', function () {
expect($window.location.href).toBe('/404');
});
});
});
My test passes the Exists with required methods check but fails Sets window location with the following error:
Error: [$injector:unpr] Unknown provider: $stateProvider <- $state
The module doesn't seem to have ui.router module loaded, hence $state service is undefined. This is fine, because real router introduces extra moving parts and is highly undesirable in unit tests.
For functional test it is normal to treat a unit as a blackbox, provide initial conditions and test the results, asserting window.location would be appropriate.
For unit test there's no need to treat a unit as a blackbox, $state service may be stubbed:
var statePromiseMock = {};
beforeEach(module('app.services', {
$state: {
go: jasmine.createSpy().and.returnValue(statePromiseMock)
}
}));
And tested like:
it('...', inject(function (HttpInterceptorService, $state) {
var state404Promise = HttpInterceptorService.responseError({ status: 404 });
expect($state.go).toHaveBeenCalledWith('404');
expect(state404Promise).toBe(statePromiseMock);
...
}))
I.e. it may be something like
describe('HttpInterceptorService', function() {
// Bindable members
var HttpInterceptorService;
var statePromiseMock = {};
beforeEach(module('app.services', {
$state: {
go: jasmine.createSpy().and.returnValue(statePromiseMock)
}
}));
// Bind references to global variables
beforeEach(inject(function(_HttpInterceptorService_) {
HttpInterceptorService = _HttpInterceptorService_;
}));
// Check service exists with methods
it('Exists with required methods', function() {
expect(HttpInterceptorService).toBeDefined();
expect(angular.isFunction(HttpInterceptorService.response)).toBe(true);
expect(angular.isFunction(HttpInterceptorService.responseError)).toBe(true);
});
it('...', inject(function($state) {
var state404Promise = HttpInterceptorService.responseError({
status: 404
});
expect($state.go).toHaveBeenCalledWith('404');
expect(state404Promise).toBe(statePromiseMock);
}))
});

Why my test failed after adding didInsertElement to fetch json in ember?

I have a simple component that it's going to fetch data after the component is inserted. It was ok until I run my test. I got this error.
Assertion Failed: You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in a run
I understand that the component is fetching data asynchronously but I'm not sure how to solve that in my integration test.
Here's my code
export default Ember.Component.extend({
didInsertElement: function() {
let source = this.get('source');
let url = apiUrl + source;
Ember.$.getJSON(url).then(function(response) {
this.set('data', response.data);
}.bind(this));
},
// something else
};
And this is my test.
moduleForComponent('panel', 'Integration | Component | panel', {
integration: true,
beforeEach () {
this.render(hbs`{{panel}}`);
}
});
test('it has source dropdown', function(assert) {
assert.equal(this.$('select[name="Source"]').length, 1);
});
Without the fetching data bit, the test runs ok.
Try wrapping the getJSON inside an Ember run loop.
So something like this:
var self = this;
Ember.run(function(){
Ember.$.getJSON(url).then(function(response) {
self.set('data', response.data);
});
});
Create a new Promise and then set the response data in the success handler.
The promise returned by Em.$.getJSON and new Ember.RSVP.Promise are different.
let self = this;
let promise = new Ember.RSVP.Promise(function() {
return Ember.$.getJSON(url);
});
promise.then((json) => {
this.set('data', json);
});

Cannot get response content in mithril

I've been trying to make a request to a NodeJS API. For the client, I am using the Mithril framework. I used their first example to make the request and obtain data:
var Model = {
getAll: function() {
return m.request({method: "GET", url: "http://localhost:3000/store/all"});
}
};
var Component = {
controller: function() {
var stores = Model.getAll();
alert(stores); // The alert box shows exactly this: function (){return arguments.length&&(a=arguments[0]),a}
alert(stores()); // Alert box: undefined
},
view: function(controller) {
...
}
};
After running this I noticed through Chrome Developer Tools that the API is responding correctly with the following:
[{"name":"Mike"},{"name":"Zeza"}]
I can't find a way to obtain this data into the controller. They mentioned that using this method, the var may hold undefined until the request is completed, so I followed the next example by adding:
var stores = m.prop([]);
Before the model and changing the request to:
return m.request({method: "GET", url: "http://localhost:3000/store/all"}).then(stores);
I might be doing something wrong because I get the same result.
The objective is to get the data from the response and send it to the view to iterate.
Explanation:
m.request is a function, m.request.then() too, that is why "store" value is:
"function (){return arguments.length&&(a=arguments[0]),a}"
"stores()" is undefined, because you do an async ajax request, so you cannot get the result immediately, need to wait a bit. If you try to run "stores()" after some delay, your data will be there. That is why you basically need promises("then" feature). Function that is passed as a parameter of "then(param)" is executed when response is ready.
Working sample:
You can start playing with this sample, and implement what you need:
var Model = {
getAll: function() {
return m.request({method: "GET", url: "http://www.w3schools.com/angular/customers.php"});
}
};
var Component = {
controller: function() {
var records = Model.getAll();
return {
records: records
}
},
view: function(ctrl) {
return m("div", [
ctrl.records().records.map(function(record) {
return m("div", record.Name);
})
]);
}
};
m.mount(document.body, Component);
If you have more questions, feel free to ask here.

How to assert that a backend endpoint has been called?

I'm building an E2E test of an Angular application using Protractor. The backend HTTP services are being mocked with $httpBackend. So far, the test looks like this:
describe('foo', function () {
it('bar', function () {
var backendMockModule = function () {
angular
.module('backendMock', [])
.run(['$httpBackend', function ($httpBackend) {
$httpBackend.whenPUT('http://localhost:8080/services/foo/bar')
.respond(function (method, url, data, header) {
return [200, {}, {}];
});
}]);
};
browser.addMockModule('backendMock', backendMockModule);
browser.get('http://localhost:8001/#/foo/bar');
element(by.id('baz')).click();
// here I would like to assert that the Angular app issued a PUT to '/foo/bar' with data = {...}
});
});
The test is a little more elaborated than this, it tests for optimistic update of the interface and other stuff. But I think this is not relevant to the this question, so I removed the other parts. The test in itself is working fine, I'm able to check that the elements on the interface are as expected. What I didn't find out is:
How to assert that the backend HTTP endpoint has been called with the correct data, method, headers, etc?
I have tried to do it like this (adding hasBeenCalled variable):
describe('foo', function () {
it('bar', function () {
var hasBeenCalled = false;
var backendMockModule = function () {
angular
.module('backendMock', [])
.run(['$httpBackend', function ($httpBackend) {
$httpBackend.whenPUT('http://localhost:8080/services/foo/bar')
.respond(function (method, url, data, header) {
hasBeenCalled = true;
return [200, {}, {}];
});
}]);
};
browser.addMockModule('backendMock', backendMockModule);
browser.get('http://localhost:8001/#/foo/bar');
element(by.id('baz')).click();
expect(hasBeenCalled).toBeTruthy();
});
});
But it does not work. I don't know exactly how the Protractor does the testing, but I imagine that it sends a serialized version of the function to the browser in the call addMockModule instead of running the test in the same process as the web page and so I'm not able to share state between the test and the browser (side question: is that correct?).
$httpBackend.flush() is needed before expect(...)...

Node Mocha Chai Async - Everything Passing even when it should fail

I was attempting to teach myself to use a Testing framework for automating tests instead of having to do them by hand. After a bit of trial and error, I finally got the unit tests to start passing ... but now, my problem is everything is passing regardless of if it should or not.
Currently I have the following code:
describe('create {authName, authPW}', function() {
it('no name', function() {
init({
path: ':memory:',
callback: function() {
var arg;
arg = {};
//arg['authName'] = 'Name';
arg['authPW'] = 'Pass';
arg['callback'] = function(r) {
// r.should.equal('create error');
r.should.equal('foobar');
done();
};
create(arg);
}
});
});
});
as you can guess ... r should NOT equal 'foobar'
What am I doing wrong here?
When creating async tests with mocha you need to let him know when it is done
describe('an asynch piece of code', function() {
var foo = new bar();
it('should call the callback with a result', function( done ) {
foo.doAsynchStuff( function( result ) {
result.should.be.ok;
done();
});
});
});
If done is present as an argument on the it then mocha will wait for the done to be called. It has a timeout of 2 seconds, that if exceeded fails the test. You can increase this timeout:
it('should resolve in less than 10 seconds', function( done ) {
this.timeout( 10000 );
foo.doAsynchStuff( function( result ) {
result.should.be.ok;
done();
});
}
it('no name', function(done) {
done has to be an argument of the function passed to it()

Categories