Using ng-describe for end-to-end testing with protractor - javascript

I've recently discovered an awesome ng-describe package that makes writing unit tests for AngularJS applications very transparent by abstracting away all of the boilerplate code you have to remember/look up and write in order to load, inject, mock or spy.
Has somebody tried to use ng-describe with protractor? Does it make sense and can we benefit from it?
One of the things that caught my eye is how easy you can mock the HTTP responses:
ngDescribe({
inject: '$http', // for making test calls
http: {
get: {
'/my/url': 42, // status 200, data 42
'/my/other/url': [202, 42], // status 202, data 42,
'/my/smart/url': function (method, url, data, headers) {
return [500, 'something is wrong'];
} // status 500, data "something is wrong"
},
post: {
// same format as GET
}
},
tests: function (deps) {
it('responds', function (done) {
deps.$http.get('/my/other/url')
.then(function (response) {
// response.status = 202
// response.data = 42
done();
});
http.flush();
});
}
});
Mocking HTTP responses usually helps to achieve a better e2e coverage and test how does UI reacts to specific situations and how does the error-handling work. This is something we are currently doing with protractor-http-mock, there are also other options which don't look as easy as it is with ng-describe.

Protractor primary is intended for E2E testing (with selenium webdriver) and that means that you need to have an actual backend hooked up (it could be a mock backend also). As the creator of Protractor wrote here, your application code runs separately with the test code and it isn't possible to gain easy access to the $http service.
By mocking the backend calls you are not doing E2E testing anymore even if you are using tool for E2E tests like Protractor. Why not to return to unit testing then. The only difference will be that you will use jQuery instead the Protractor API and the tests will be run with Karma. Then you can easily use ng-describe and $httpBackend which primary are intended to be used in unit tests.
However if you like to continue with this approach you can check the comments in this Protractor issue. There are several guys that are proposing solutions for this problem and as mentioned you are already using one of them. But in this case ng-describe won't help you much.
I hope that this answers your question.

Related

Run Jest tests multiple times if it fails

I'm using Jest for testing some API endpoints, but these endpoints might fail falsy at some times, because of network issues and not my program bugs. So I want to do the test again multiple times (5 times for example) with a delay, and if all of these tries failed, Jest should report a failure.
What is the best way to achieve this? Does Jest or other libraries provide a solution? Or should I write my own program with something like setInterval?
For testing you should not ideally be making network calls as they slow down your tests. Moreover you might be communicating to an external API which ideally should not be part of your test code. Also can lead to false negatives as already observed by you. You can do something like below:
async function getFirstMovieTitle() {
const response = await axios.get('https://dummyMovieApi/movies');
return response.data[0].title;
}
For testing above network call you should mock your axios get request in your test. To test the above code, you should
jest.mock('axios');
it('returns the title of the first movie', async () => {
axios.get.mockResolvedValue({
data: [
{
title: 'First Movie'
},
{
title: 'Second Movie'
}
]
});
const title = await getFirstMovieTitle();
expect(title).toEqual('First Movie');
});
Try to read more about mocking in jest to understand better

Asynchronous code in custom ESLint rules

The Story and Motivation:
We have a rather huge end-to-end Protractor test codebase. Sometimes it happens that a test waits for a specific fix to be implemented - usually as a part of a TDD approach and to demonstrate how a problem is reproduced and what is the intended behavior. What we are currently doing is using Jasmine's pending() with a Jira issue number inside. Example:
pending("Missing functionality (AP-1234)", function () {
// some testing is done here
});
Now, we'd like to know when we can rename the pending() back to it() and run the test. Or, in other words, when the issue AP-1234 is resolved or sent to testing.
The Current Approach:
At the moment, I'm trying to solve it with a custom ESLint rule, jira NodeJS module, and Q. The custom ESLint rule searches for pending() calls with at least one argument. Extracts the ticket numbers in format of AP- followed by 4 digits and uses jira.findIssue() to check its status in Jira. If status is Resolved - report an error.
Here is what I've got so far:
"use strict";
var JiraApi = require("jira").JiraApi,
Q = require('q');
var jira = new JiraApi("https",
"jira.url.com",
"443",
"user",
"password",
"2");
module.exports = function (context) {
var jiraTicketRegex = /AP\-\d+/g;
return {
CallExpression: function (node) {
if (node.callee.name === "pending" && node.arguments.length > 0) {
var match = node.arguments[0].value.match(jiraTicketRegex);
if (match) {
match.forEach(function(ticket) {
console.log(ticket); // I see the ticket numbers printed
getTicket(ticket).then(function (status) {
console.log(status); // I don't see statuses printed
if (status === "Resolved") {
context.report(node, 'Ticket {{ticket}} is already resolved.', {
ticket: ticket
})
}
});
});
}
}
}
}
};
Where getTicket() is defined as:
function getTicket(ticket) {
var deferred = Q.defer();
jira.findIssue(ticket, function(error, issue) {
if (error) {
deferred.reject(new Error(error));
} else {
deferred.resolve(issue.fields.status.name);
}
});
return deferred.promise;
}
The problem is: currently, it successfully extracts the ticket numbers from the pending() calls, but doesn't print ticket statuses. No errors though.
The Question:
The general question is, I guess, would be: can I use asynchronous code blocks, wait for callbacks, resolve promises in custom ESLint rules? And, if not, what are my options?
A more specific question would be: what am I doing wrong and how can I use Node.js jira module with ESLint?
Would appreciate any insights or alternative approaches.
The short answer is - no, you can't use asynchronous code inside of the rules. ESLint is synchronous and heavily relies on EventEmitter when it walks AST. It would be very hard to modify ESLint code to be async, but at the same time guarantee that events will be emitted in the right order.
I think your only choice might be to write a sync rule that outputs enough information into the error message, then use one of the parsable formatters like JSON or UNIX and then create another application that you can pipe ESLint output to and do a async lookup in Jira based on the error message.
These answers remain valid in 2018.
For some insights from the eslint devs, see this conversation we had on their mailing list.
For a working example, in my "pseudo eslint plugin" I opted to use expensive but synchronous APIs and warn users about how best to use the "plugin" in their CI process.
Note: it does not answer original question about support of async code in ESLint custom rules, but provides an alternative solution to the issue.
I personally would not use ESLint in this case, it is supposed to be used to check if your code is written correctly and if you follow style guides; from my point of view missing tests is not the part of code check, it's more like your team internal processes. Also, this kind of requests may slow your ESLint executions significantly, if anyone runs it in real-time in their editor, calls will be made very often and will slow down the entire check. I would make this JIRA check a part of Protractor flow, so if the ticket is resolved, you will get a failed Protractor spec. (copied from the comment to make the answer complete)
Jasmine allows to mark specs as pending using xit(). I am not sure about pending() though, it works weird in Protractor. Also, Jasmine allows to call pending() inside a spec, so it will be marked as pending, but it is not implemented for Protractor yet (see issue). Knowing that, I would use a custom helper to define "pending specs", which should be checked for JIRA issue status. I guess you can still use Q to work with promises, I'll just post an alternative using WebDriver promises without external dependencies. Here is a modified version of getTicket():
function getTicketStatus(ticket) {
// Using WebDriver promises
var deferred = protractor.promise.defer();
jira.findIssue(ticket, function(error, issue) {
if (error) {
deferred.reject(new Error(error));
} else {
deferred.fulfill(issue.fields.status.name);
}
});
return deferred.promise;
}
Then there is a custom helper function:
function jira(name) {
// Display as pending in reporter results, remove when pending() is supported
xit(name);
// Using Jasmine Async API because Jira request is not a part of Control Flow
it(name, function (done) {
getTicketStatus().then(function (status) {
if (status === 'Resolved') {
done.fail('Ticket "' + name + '" is already resolved.');
} else {
done();
// pending() is not supported yet https://github.com/angular/protractor/issues/2454
// pending();
}
}, function (error) {
done.fail(error);
});
});
}
Usage example:
jira('Missing functionality (AP-1234)', function () {
//
});
jira('Missing functionality (AP-1235)');
In case if request to JIRA fails or issue has a status Resolved, you will get a failed spec (using Jasmine async API). In all situations you will still have this spec duplicated as pending in reporter results. I hope it can be improved, when pending() functionality inside a spec is implemented.

How do I test a function that sends a request in browser that requires authentication?

I'm in the process of writing unit tests in Mocha and Chai, and I'm trying to figure out how to test a function that that uses the fetch API to send a request that requires a session to access. This function is an ES6 method that returns a thenable object.
_auth(record) {
var authMetadata = this._getAuthMetadata(record);
var authUrl = `/${this._getPortal(window.location.pathname)}/tlist_child_auth.html?${this._encodeUri(authMetadata)}`;
if (getPortal() !== 'guardian') {
authUrl += `&frn=${this.coreTableNumber}${this.foreignKey}`;
}
return fetch(authUrl, {
credentials: 'include'
}).then(function(rawData) {
return rawData.text();
});
}
tlist_child_auth.html is the page that requires session authorization to access. Is there any easy way to do this, or should I seek to "invent the wheel"?
With unit testing, the idea is to test the unit that you're interested in, and only that. In this instance, the unit makes use of fetch, which itself goes off and talks to websites, so in effect your unit test ends up doing a round trip to a website so you end up "testing" that too. Not what you want in a unit test.
The correct way to test just the part you're interested in is to mock the fetch function, and use the mock to verify it is called in the way you expect, plus also return data from the mock to your unit that it can use to complete the test. You may perform other checks to ensure your unit has processed the return in the way you expected.
It appears that there is at least one npm package available for mocking fetch.

Calling web services from Jasmine Test

I am attempting to write some black box tests to some web services. In an attempt to do this, I'm using Jasmine. I would like to know how to call a web service from Jasmine. I currently have the following setup:
describe('Web Service', function () {
describe('-> endpoint', function() {
describe('-> create', function() {
var response = null;
beforeEach(function(done) {
// execute a call to the web service here
if (done) {
done();
}
});
it('should run correctly', function(done) {
if (done) {
done();
}
});
});
});
});
How do I call a web service from Jasmine?
Thank you!
You should use something like nock to mock a response and assert that your code has called the service and received the correct response and then did its job correctly, or, if you really want nock can even allow your code to perform the actual call to the web service.
Otherwise if you just want to call the webservice from within your test you most likely have an API client somewhere where you can just perform the call yourself.
describe('My Test', function (done) {
beforeEach(function () {
var myApiClient = new MyApiClient();
//unless you're using promises
this.response = myApiClient.synchronousGet('http://someURL.com');
});
});
Jasmine itself isn't, and shouldn't be, responsible for calling your web services, that should come from within your unit under test.
You shouldn't send ajax requests from your tests because you'll be dependant of the services to run your tests... And a bad response from your webservices doesn't absolutely mean that your code is not running correctly.
A good way to do it is to mock the ajax request (as limelights said), but Jasmine has an ajax module ready to help you with this. See http://jasmine.github.io/edge/ajax.html
This Jasmine plugin will allow you to catch the calls to a service and replace it with your fresh configured response that will be always the same (because you should know the exact response to test correctly your service.
Anyway, if you absolutely want to call your service, I think you'll have to code it in JS in your tests (not recommanded to write such code in your tests. Better write function that will be tested too). As I know, Jasmine doesn't provide you this kind of helper, it's just for tests purposes.

Angular.js promise not resolving when unit testing service with karma

I am trying to unit test an Angular.js service, and need to set an expect on a promise returned from a Mock service (using Jasmine). I am using the karma unit testing framework. The relevant code snippet is below:
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(false).toBeTruthy(); // If this test passes, there is something going wrong!
expect(token).not.toBeNull(); // The token should be ValidToken
expect(token).toBe('ValidToken');
});
The complete unit test code can be seen here.
The problem is the promise.then statement never fires when karma is executing. Hence, none of my expect statements are executed.
In my controller tests, I use $scope.$digest() to resolve the promises, but I am not clear on how to do this in a service test. As I thought there was no notion of 'scope' in a service test.
Do I have the wrong end of the stick here? Do I need to injecct $rootScope into my service test and then use $digest? Or, is there another way?
I had this problem and resolved it by simply putting a
$rootScope.$apply() at the end of my test
Your FacebookService might be the issue, as suggested by #mpm. Are you sure it doesn't have any http calls happening inside of that Facebook dependency which wouldn't be occurring during unit testing? Are you certain that resolve has been called on the deferred yet?
Assuming that you are using ngFacebook/ngModule a quick note before the solution/ideas is that this project does not have unit tests ! Are you sure you want to use this project ?
I did a quick scan of your Unit Tests on Github and found following missing:-
1) Module initialization.
ngFacebook needs that or you need to initialize your module that does the same thing.
beforeEach(module('ngFacebook'));
OR
beforeEach(module('yieldtome'));
2) Seriously consider mocking ngFacebook module
At unit level tests you are testing your code within a mocked bubble where outside interfaces are stubbed out.
Otherwise) Try adding calling the API as below:-
$rootScope.$apply(function() {
this.FacebookService.getFacebookToken().then(function(){
//your expect code here
});
});
$httpBackend.flush();//mock any anticipated outgoing requests as per [$httpBackend][2]
beforeEach(function(){
var self=this;
inject(function($rootScope,Facebook){
self.$rootScope=$rootScope;
self.Facebook=Facebook;
});
})
it('resolves unless sourcecode broken',function(done){
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = this.FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(token).toBe('ValidToken');
done();
});
$rootscope.$apply();
});
https://docs.angularjs.org/api/ng/service/$q
I agree with the above answers that a service should have nothing to do with $rootScope.
In my case had a $q promise, that used a second service internally resolving to a promise as well. No way to resolve the external one, unless I added $rootScope.$digest() into my service code (not the test)...
I ended-up writing this quick shim for $q to use in my tests, but be careful, as it's just an example and not a complete $q implementation.
beforeEach(module(function($provide) {
$provide.value('$q', {
defer: function() {
var _resolve, _reject;
return {
promise: {
then: function (resolve, reject) {
_resolve = resolve;
_reject = reject;
}
},
resolve: function (data) {
window.setTimeout(_resolve, 0, data);
},
reject: function (data) {
window.setTimeout(_reject, 0, data);
}
};
}
});
}));
Hope it will be useful to someone, or if you have any feedback.
Thanks.

Categories