I have a problem with a small karma unit test that should check a simple decryption/encryption service.
The thing is, if I call the following code "manual" (i.e., within my running angular app) everything is fine and I receive the expected test output:
this.encryptDataAsync('Hello World of Encryption','b4b63cd1a64dbef72fefe2eb3e3fc3eb').then((encryptedValue : string) : void => {
console.log('1',encryptedValue);
this.decryptDataAsync(encryptedValue,'b4b63cd1a64dbef72fefe2eb3e3fc3eb').then(function(decryptedValue : string) : void{
console.log('2',decryptedValue);
});
});
As soon as I try to run this Karma/Jasmine unit test
describe('simple encryption/decryption', function() {
var results = '';
beforeEach(function(done) {
_cryptoService.encryptDataAsync('ABC','b4b63cd1a64dbef72fefe2eb3e3fc3eb').then(function (encryptedValue){
console.log('1');
_cryptoService.decryptDataAsync(encryptedValue,'b4b63cd1a64dbef72fefe2eb3e3fc3eb').then(function(decryptedValue){
console.log('2');
results = decryptedValue;
done();
});
});
});
it("check results", function(done){
expect(results).toBe('ABC');
done();
}, 3000);
});
I never reach console.log('1') nor '2'. I can confirm this while debugging the unit test. However, this is the only unit test that fails in the complete suite, so I guess it won't by a problem with modules, etc.
Is there a general problem with my test case? I would have expected that I can use the then functions to handle my test case and, afterwards, call the done() function to invoke the assertion part.
Update/Edit:
The service uses webcrypto as a library. It is complete independent of angular besides being an angular service (so, no variables on scopes, etc)
I needed to call scope.apply since "$q is integrated with the $rootScope.Scope Scope model observation mechanism in angular, which means faster propagation of resolution or rejection into your models and avoiding unnecessary browser repaints, which would result in flickering UI."
Related
I am trying to ensure my unit test case covers the scenario of a promise never being resolved, and I am having some issues.
A code block is worth a thousand words, so..
it('returns my resource', function() {
var myUser = {name:'Bob'}
$httpBackend.expectGET('/myresource').respond(200,myUser);
myService.getMyUser()
.then(function(data) {
expect(data).toEqual(myUser);
},fail);
});
This is all well and good, and tests that the response is as expected and also fails the test should the promise be rejected.
However, should the promise never be resolved at all, the test passes and I would like it to fail. I am caching this request in myService like so:
var cachedUser;
function getMyUser(forceUpdate) {
var deferred = $q.defer();
if (cachedUser && !forceUpdate) {
deferred.resolve(cachedUser);
} else {
$http.get('/myresource')
.then(function(data) {
cachedUser = data;
deferred.resolve(data);
},function(error) {
deferred.reject(error);
});
}
return deferred.promise;
}
Now in the above scenario, if one were to remove the line "deferred.resolve(data)" from within the $http.get, the test would still pass. This is because the callback function containing the expectation for data.toEqual(myUser) is never run. However the test should fail because removing that line breaks the purpose of this function.
I have tried within the test code to make the positive callback a spy and expect the spy toHaveBeenCalled, but that seems to run before the promise is resolved and fails even though I can see that the spy's function ran via a console log.
I have also tried within the unit test putting the promise in a variable and then expect(promise.$$state.status).toEqual(1), but again it appears that this expectation is run before the promise is resolved and so the test fails when it should pass.
Please be sure you understand the problem before answering, post a comment if I have not been clear.
The issue turned out to be that when dealing with $httpBackend, you must flush() the backend for requests to go through. I was doing this in an afterEach() block around every test, and so when trying to test the state of the promise from within the test block the promise had not actually been resolved yet.
By moving $httpBackend.flush() to within the unit test, the promise returned did indeed update its status from 0 to 1.
Here is the final unit test code:
it('returns my resource', function() {
var myUser = {name:'Bob'}
var promise;
$httpBackend.expectGET('/myresource').respond(200,myUser);
promise = myService.getMyUser()
.then(function(data) {
expect(data).toEqual(myUser);
},fail);
$httpBackend.flush();
expect(promise.$$state.status).toEqual(1);
});
Assume we have a service which calls api and we use this service to do some logic in a controller.
What is better to use?
user = $injector.get('userSrv');
var myFixture = angular.fromJson(window.__html__['mydata.json']);
$httpBackend.whenGET('url/').respond(myFixture);
user.getGender();
or just using
beforeEach(module(function($provide) {
$provide.service('userSrv', function(){
return {
getGender: function(){
return 'something';
}
}
});
})
Both should be used, but in different tests.
In controller spec, a service is supposed to be mocked, because the unit under test is a controller.
In service spec, http request is supposed to be mocked, because the unit under test is a service (this allows to keep the test synchronous and independent from the backend, it is not possible to perform real requests with ngMock any way).
This allows to unambiguously determine which unit failed when a test becomes red.
So I had a situation where we had to hit a "restful" service not under our control, where in order to get json back from the service on a GET call, we have to pass Content-Type="application/json" in the header. Only problem is that Angular strips the Content-Type from request headers on a GET. I found a blog post that suggested using a decorator on $httpBackend that allows us to intercept the call before it is sent and add back the content type:
angular
.module('MyApp')
.decorator('$httpBackend', [
'$delegate', function($delegate) {
return function() {
var contentType, headers;
headers = arguments[4];
contentType = headers != null ? headers['X-Force-Content-Type'] : null;
if (contentType != null && headers['Content-Type'] == null)
headers['Content-Type'] = contentType;
return $delegate.apply(null, arguments);
};
}]);
so, that works beautifully! Now our problem is that it has broken all unit tests where we used the mock $httpBackend service. The only error we get is "undefined".
Ex. unit test method:
it('should return service.model.error if service returns an exception code from EndProject',
inject(function($httpBackend) {
var mockResponse = sinon.stub({ 'exception': 'Unable to retrieve service data' });
$httpBackend.whenPUT(this.endProjectUrl).respond(mockResponse);
var data;
this.service.EndProject().then(function(fetchedData) {
data = fetchedData;
});
$httpBackend.flush();
expect(data.error.state).toBe(true);
expect(data.error.message).toEqual('Unable to retrieve service data');
}));
PhantomJS 2.1.1 (Mac OS X 0.0.0) projectService EndProject should return service.model.error if service returns an exception code from EndProject FAILED
undefined
/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/src/app/components/services/project/projectService.spec.js:213:41
invoke#/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular/angular.js:4560:22
workFn#/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular-mocks/angular-mocks.js:2518:26
The listed decorator covers simple monkey-patching scenario where patched function isn't a constructor and has no static properties and methods.
This is true for $httpBackend in ng module, it is just a factory function with no extra properies.
This is not true for ngMock and ngMockE2E modules that override $httpBackend and have static methods, at least some of them are documented.
This means that generally safe recipe (it doesn't cover non-enumerable and inherited properties) for monkey-patching a factory function is
app.decorator('$httpBackend', ['$delegate', function ($delegate) {
var $httpBackend = function () {
...
return $delegate.apply(null, arguments);
};
angular.extend($httpBackend, $delegate);
return $httpBackend;
}]);
Regardless of that, it is a good habit to modularize the app to the level where units can be tested in isolation with no excessive moving parts (this issue is an expressive example why this is important). It is convenient to have app (bootstrapped in production), app.e2e (bootstrapped in e2e tests), app.common (common denominator), app.unitA (loaded in app.common and can be loaded separately in unit test), etc.
Most of application-wide code (config and run blocks, routing) may be moved to separate modules and loaded only in modules that directly depend on them. Unless this is a spec that tests decorator unit itself, decorator module shouldn't be loaded.
Also notice that Chrome may offer superior experience than PhantomJS when debugging spec errors.
While I marked estus's answer as the solution, based purely on what my question was...in the end, ultimately it wasn't the end result we went with. In a case of not seeing the forest through the trees, the simplest solution was to add an empty data element to the $http call's config. I had tried it before and it didn't work (or so it seemed), but after playing with it again, it did in fact work and we were able to remove the decorator from the application.
return $http.get(getItemInformationUrl + params, { dataType: 'json', data: '', headers: {'Content-Type': 'application/json'} }).then(getItemInformationCompleted).catch(getItemInformationFailed);
I am writing end-to-end tests for my AngularJS-based application using Protractor. Some cases require using mocks to test - for example, a network connection issue. If an AJAX request to server fails, the user must see a warning message.
My mocks are registered in the application as services. I want them to be accessible to the tests to write something like this:
var proxy;
beforeEach(function() { proxy = getProxyMock(); });
it("When network is OK, request succeeds", function(done) {
proxy.networkAvailable = true;
element(by.id('loginButton')).click().then(function() {
expect(element(by.id('error')).count()).toEqual(0);
done();
});
});
it("When network is faulty, message is displayed", function(done) {
proxy.networkAvailable = false;
element(by.id('loginButton')).click().then(function() {
expect(element(by.id('error')).count()).toEqual(1);
done();
});
});
How do I implement the getProxyMock function to pass an object from the application to the test? I can store proxies in the window object of the app, but still do not know how to access it.
After some reading and understanding the testing process a bit better, it turned to be impossible. The tests are executed in NodeJS, and the frontend code in a browser - Javascript object instances cannot be truly shared between two different processes.
However, there is a workaround: you can execute a script inside browser.
First, your frontend code must provide some sort of service locator, like this:
angular.module('myModule', [])
.service('proxy', NetworkProxy)
.run(function(proxy) {
window.MY_SERVICES = {
proxy: proxy,
};
});
Then, the test goes like this:
it("Testing the script", function(done) {
browser.executeScript(function() {
window.MY_SERVICES.proxy.networkAvailable = false;
});
element(by.id('loginButton')).click().then(function() {
expect(element.all(by.id('error')).count()).toEqual(1);
done();
});
});
Please note that when you use executeScript, the function is serialized to be sent to browser for execution. This puts some limitations worth keeping in mind: if your script function returns a value, it is a clone of the original object from browser. Updating the returned value will not modify the original! For the same reason, you cannot use closures in the function.
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.