How to unit test this simple function with Angular 6 / Jasmine - jasmine

I have this below method in one of my components. How can I write a unit test for it?
getInitialSeats() {
for (let i = 0; i < 100; i++) {
i = i + 1;
this.seatObj = {
seatName: "Seat- " + i,
seatId: "seat_" + i
}
this.totalSeats.push(this.seatObj);
this.seatObj = {};
i = i - 1;
}
}

Before writing the unit test, I would suggest that you improve your function a bit. There is some code in there that you don't necessarily need. Have a look at this improved function that does the exact same thing.
getInitialSeats() {
for (let i = 1; i <= 100; i++) {
this.totalSeats.push({
seatName: "Seat- " + i,
seatId: "seat_" + i
});
}
}
To test this function I would just write a very simple test case like this (I assume this function is in a component):
it('should test the initial seats generation', () => {
// test the before state, i assume the array will be empty beforehand
expect(component.totalSeats.length).toBe(0);
// invoke the function
component.getInitialSeats();
// test the amount of seats generated
expect(component.totalSeats.length).toBe(100);
// test some of the objects generated
expect(component.totalSeats[0]).toEqual({ seatName: 'Seat-1', seatId: 'seat_1'});
expect(component.totalSeats[99]).toEqual({ seatName: 'Seat-100', seatId: 'seat_100'});
});
If this function is called somewhere in your component based on an event/interaction then you could set up a spy to check whether it was sucessfully called. A test could look like this:
it('should test the initial seats generation', () => {
// setup spy and check it hasn't been called yet
const spy = spyOn(component, 'getInitialSeats').and.callThrough();
expect(spy).not.toHaveBeenCalled();
// do something that will invoke the function, here we just call it ourselves
component.getInitialSeats();
// check spy
expect(spy).toHaveBeenCalledTimes(1);
// test the amount of seats generated
expect(component.totalSeats.length).toBe(100);
// test some of the objects generated
expect(component.totalSeats[0]).toEqual({ seatName: 'Seat-1', seatId: 'seat_1'});
expect(component.totalSeats[99]).toEqual({ seatName: 'Seat-100', seatId: 'seat_100'});
});

Related

How to describe a testcase inside an it() in jasmine

This is my code so far:
import diceroll, {maxVal, minVal} from './index';
let testset=[
// definition, min, max
["1",1,1],
["w6",1,6],
["1w6",1,6],
["2w6",2,12],
["2w6+12",14,24],
["2w6+12+2w3",16,30],
["3w6-3",0,15],
];
describe('lib/diceroll', () => {
it('should parse correctly', () => {
for (let i = 0; i < testset.length; i++) {
let definition = testset[i][0];
let minToBe = testset[i][1];
let maxToBe = testset[i][2];
let min = minVal(definition);
let max = maxVal(definition);
// #todo: OnFailure tell me the current definition!
expect(minVal(definition)).toBe(minToBe);
expect(maxVal(definition)).toBe(maxToBe);
for (let n = 0; n < 100; n++) {
let r = diceroll(definition);
expect(r).toBeLessThanOrEqual(minToBe);
expect(r).toBeGreaterThanOrEqual(maxToBe);
}
}
});
});
My problem: If some expectation failes I do not know which diceroll-definition was failing. I tried to call a describe() inside an it() - which is not allowed.
What is the best practice here? Using it() inside my testset-loop? Or am I doing something completely off here?
Jasmine matchers have an optional second argument:
(method) jasmine.Matchers<number>.toBeLessThanOrEqual(expected: number, expectationFailOutput?: any): boolean
So you could write something like the following to output where the failure occurred:
for (let n = 0; n < 100; n++) {
let r = diceroll(definition);
expect(r).toBeLessThanOrEqual(minToBe, 'failed when i='+i+' n='+n);
expect(r).toBeGreaterThanOrEqual(maxToBe, 'failed when i='+i+' n='+n);
}

How can I run a truffle test on an existing contract?

I'm working on a simple ethereum contract and it's truffle test counterpart, but the issue I'm running into is that I need the test to call an old deployment of the contract, instead of redeploying it every time.
In the truffle documentation, it says the contract() function should be used when the contract is to be redeployed and mocha's describe() in all other cases, but even using describe, the geth client reports redeploying the contract evey time.
Here is the test:
var md5 = require('md5');
var AuditRecord = artifacts.require("AuditRecord");
describe('AuditRecord', function() {
before(function() {
audit = AuditRecord.at('0x30ad3ceaf3f04696d1f7c8c4fbb9cfe4f7041822');
for (var i = 0; i < 10; ++i) {
if (Math.random() < 0.3) {
audit.enter(i, i, md5("test"), md5("data"), Date.now().toFixed());
} else {
audit.enter(i, i, md5("special_case"), md5("data"), Date.now().toFixed());
}
}
return audit.LogRecord({}, { fromBlock: 0, toBlock: 'latest'});
});
it("should read data", function() {
auditLog = AuditRecord.at('0x30ad3ceaf3f04696d1f7c8c4fbb9cfe4f7041822').LogRecord({}, { fromBlock: 0, toBlock: 'latest'});
auditLog.get(function(err, data) {
console.log("\n\n\n\t.:::: testing lookup:\n")
if (err) {
console.log(err);
return false;
}
for (obj in data) {
console.log("entry: # " + Date(data[obj].args.timestamp).toString());
console.log("\tuser: " + web3.toAscii(data[obj].args.user));
console.log("\tpatient: " + web3.toAscii(data[obj].args.patient));
console.log("\toperation: " + data[obj].args.operation);
//console.log(JSON.stringify(data[obj].args.));
}
assert(10 < data.length);
});
})
})
The test works in the sense that it finds my previously deployed contract at the hardcoded address, but for some reason it also deploys both the migrations.sol and auditrecord.sol on unrelated addresses every time. My aim is to refer to the same contract every time this test is ran.
Is there a way to achieve this?

Does Jasmine have an after-advice spy?

When spying on a method, we can either callThrough (use original implementation) or callFake (use a custom implementation).
What I want is a behaviour similar to callThrough but inspect/modify its return value before returning it to the caller.
So I can do something like this:
spyOn(foo, "fetch").and.afterCall(function(result) {
expect(result).toBeDefined();
result.bar = "baz";
return result;
});
Right now the simplest way is doing something like this:
var original = foo.fetch;
foo.fetch = function() {
var result = original.apply(this, arguments);
expect(result).toBeDefined();
result.bar = "baz";
return result;
}
Which is somewhat annoying because now I have to manually restore the spy instead of having the framework automatically does it for me.
Does Jasmine have an after-advice spy?
Generally: no.
You could extend the SpyStrategy object with such a function though:
this.callThroughAndModify = function(resultModifier) {
var result;
plan = function() {
result = originalFn.apply(this, arguments);
return resultModifier(result);
};
return getSpy();
};
You've to clone the above SpyStrategy file and insert that method.
Usage:
var obj = {
fn: function(a) { return a * 2; }
};
spyOn(obj, "fn").and.callThroughAndModify(function(result) {
console.log("Original result: ", result);
return 1;
});
expect(obj.fn(2)).toBe(1);
Drawbacks:
You've to replace the whole SpyStrategy.js
You've to load that script before Jasmine initializes the original SpyStrategy at boot

How to retry failures with $q.all

I have some code that saves data using Breeze and reports progress over multiple saves that is working reasonably well.
However, sometimes a save will timeout, and I'd like to retry it once automatically. (Currently the user is shown an error and has to retry manually)
I am struggling to find an appropriate way to do this, but I am confused by promises, so I'd appreciate some help.
Here is my code:
//I'm using Breeze, but because the save takes so long, I
//want to break the changes down into chunks and report progress
//as each chunk is saved....
var surveys = EntityQuery
.from('PropertySurveys')
.using(manager)
.executeLocally();
var promises = [];
var fails = [];
var so = new SaveOptions({ allowConcurrentSaves: false});
var count = 0;
//...so I iterate through the surveys, creating a promise for each survey...
for (var i = 0, len = surveys.length; i < len; i++) {
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', surveys[i].ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
promises.push(manager
.saveChanges(changes, so)
.then(function () {
//reporting progress
count++;
logger.info('Uploaded ' + count + ' of ' + promises.length);
},
function () {
//could I retry the fail here?
fails.push(changes);
}
));
}
}
//....then I use $q.all to execute the promises
return $q.all(promises).then(function () {
if (fails.length > 0) {
//could I retry the fails here?
saveFail();
}
else {
saveSuccess();
}
});
Edit
To clarify why I have been attempting this:
I have an http interceptor that sets a timeout on all http requests. When a request times out, the timeout is adjusted upwards, the user is displayed an error message, telling them they can retry with a longer wait if they wish.
Sending all the changes in one http request is looking like it could take several minutes, so I decided to break the changes down into several http requests, reporting progress as each request succeeds.
Now, some requests in the batch might timeout and some might not.
Then I had the bright idea that I would set a low timeout for the http request to start with and automatically increase it. But the batch is sent asynchronously with the same timeout setting and the time is adjusted for each failure. That is no good.
To solve this I wanted to move the timeout adjustment after the batch completes, then also retry all requests.
To be honest I'm not so sure an automatic timeout adjustment and retry is such a great idea in the first place. And even if it was, it would probably be better in a situation where http requests were made one after another - which I've also been looking at: https://stackoverflow.com/a/25730751/150342
Orchestrating retries downstream of $q.all() is possible but would be very messy indeed. It's far simpler to perform retries before aggregating the promises.
You could exploit closures and retry-counters but it's cleaner to build a catch chain :
function retry(fn, n) {
/*
* Description: perform an arbitrary asynchronous function,
* and, on error, retry up to n times.
* Returns: promise
*/
var p = fn(); // first try
for(var i=0; i<n; i++) {
p = p.catch(function(error) {
// possibly log error here to make it observable
return fn(); // retry
});
}
return p;
}
Now, amend your for loop :
use Function.prototype.bind() to define each save as a function with bound-in parameters.
pass that function to retry().
push the promise returned by retry().then(...) onto the promises array.
var query, graph, changes, saveFn;
for (var i = 0, len = surveys.length; i < len; i++) {
query = ...; // as before
graph = ...; // as before
changes = ...; // as before
if (changes.length > 0) {
saveFn = manager.saveChanges.bind(manager, changes, so); // this is what needs to be tried/retried
promises.push(retry(saveFn, 1).then(function() {
// as before
}, function () {
// as before
}));
}
}
return $q.all(promises)... // as before
EDIT
It's not clear why you might want to retry downsteam of $q.all(). If it's a matter of introducing some delay before retrying, the simplest way would be to do within the pattern above.
However, if retrying downstream of $q.all() is a firm requirement, here's a cleanish recursive solution that allows any number of retries, with minimal need for outer vars :
var surveys = //as before
var limit = 2;
function save(changes) {
return manager.saveChanges(changes, so).then(function () {
return true; // true signifies success
}, function (error) {
logger.error('Save Failed');
return changes; // retry (subject to limit)
});
}
function saveChanges(changes_array, tries) {
tries = tries || 0;
if(tries >= limit) {
throw new Error('After ' + tries + ' tries, ' + changes_array.length + ' changes objects were still unsaved.');
}
if(changes_array.length > 0) {
logger.info('Starting try number ' + (tries+1) + ' comprising ' + changes_array.length + ' changes objects');
return $q.all(changes_array.map(save)).then(function(results) {
var successes = results.filter(function() { return item === true; };
var failures = results.filter(function() { return item !== true; }
logger.info('Uploaded ' + successes.length + ' of ' + changes_array.length);
return saveChanges(failures), tries + 1); // recursive call.
});
} else {
return $q(); // return a resolved promise
}
}
//using reduce to populate an array of changes
//the second parameter passed to the reduce method is the initial value
//for memo - in this case an empty array
var changes_array = surveys.reduce(function (memo, survey) {
//memo is the return value from the previous call to the function
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', survey.ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
memo.push(changes)
}
return memo;
}, []);
return saveChanges(changes_array).then(saveSuccess, saveFail);
Progress reporting is slightly different here. With a little more thought it could be made more like in your own answer.
This is a very rough idea of how to solve it.
var promises = [];
var LIMIT = 3 // 3 tris per promise.
data.forEach(function(chunk) {
promises.push(tryOrFail({
data: chunk,
retries: 0
}));
});
function tryOrFail(data) {
if (data.tries === LIMIT) return $q.reject();
++data.tries;
return processChunk(data.chunk)
.catch(function() {
//Some error handling here
++data.tries;
return tryOrFail(data);
});
}
$q.all(promises) //...
Two useful answers here, but having worked through this I have concluded that immediate retries is not really going to work for me.
I want to wait for the first batch to complete, then if the failures are because of timeouts, increase the timeout allowance, before retrying failures.
So I took Juan Stiza's example and modified it to do what I want. i.e. retry failures with $q.all
My code now looks like this:
var surveys = //as before
var successes = 0;
var retries = 0;
var failedChanges = [];
//The saveChanges also keeps a track of retries, successes and fails
//it resolves first time through, and rejects second time
//it might be better written as two functions - a save and a retry
function saveChanges(data) {
if (data.retrying) {
retries++;
logger.info('Retrying ' + retries + ' of ' + failedChanges.length);
}
return manager
.saveChanges(data.changes, so)
.then(function () {
successes++;
logger.info('Uploaded ' + successes + ' of ' + promises.length);
},
function (error) {
if (!data.retrying) {
//store the changes and resolve the promise
//so that saveChanges can be called again after the call to $q.all
failedChanges.push(data.changes);
return; //resolved
}
logger.error('Retry Failed');
return $q.reject();
});
}
//using map instead of a for loop to call saveChanges
//and store the returned promises in an array
var promises = surveys.map(function (survey) {
var changes = //as before
return saveChanges({ changes: changes, retrying: false });
});
logger.info('Starting data upload');
return $q.all(promises).then(function () {
if (failedChanges.length > 0) {
var retries = failedChanges.map(function (data) {
return saveChanges({ changes: data, retrying: true });
});
return $q.all(retries).then(saveSuccess, saveFail);
}
else {
saveSuccess();
}
});

Nothing happens with my event listener on click in javascript

I have these functions :
createTreeItem: function (num, val)
{
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var i = document.createElementNS(XUL_NS, "treeitem");
var r = document.createElementNS(XUL_NS, "treerow");
var c1 = document.createElementNS(XUL_NS, 'treecell');
var c2 = document.createElementNS(XUL_NS, 'treecell');
var c3 = document.createElementNS(XUL_NS, 'treecell');
i.setAttribute("container", true);
i.setAttribute("open", true);
c1.setAttribute("label", num);
c2.setAttribute("label", val);
c3.setAttribute("value", false);
r.appendChild(c1);
r.appendChild(c2);
r.appendChild(c3);
i.appendChild(r);
i.addEventListener("click", test, false);
return i;
}
test: function ()
{
alert("zero");
}
func: function (liste)
{
try
{
root = document.getElementById("treeRoot");
var current;
for(o in liste)
{
current = createTreeItem(liste[o].id, liste[o].nom_scenario);
root.appendChild(current);
}
}
catch(e)
{
alert(e);
}
}
I am creating elements in a tree and I would like to add event listeners on each element created. The problem is that nothing happens.
In the code, Liste is the response of a json request. It contains all the elements I want to create in my xul file.
I'm not super familiar with this syntax, but my bet is that the test function isn't being 'hoisted' because of how it's being defined. try moving the 'test' function above the 'createTreeItem' function or just defining test like so:
function test() {
...
}
That way when it gets evaluated it will be 'hoisted' to the top so that when you try to add it as the action for the click event, it'll be defined. Not 100% sure this is correct but if I had to bet...

Resources