Unit testing with external library in Jasmine - javascript

How to unit test your code if it's heavily belongs to external library and within each of its methods it calls some external library function.
If everything to mock, than code coverage like Istanbul don't count those lines mocked. Who has experience in unit testing with involvement of external dependencies and libraries, what is the best practice?
For instance, we have 2 internal functions and 3 external library functions.
If mock those external ones, than Istanbul doesn't count those lines as covered.
function internalFoo1(input) {
var result = internalFoo2(input*2);
var finalResult = externalLibraryBar1(result);
return result;
};
function internalFoo2(value) {
var operation = externalLibraryBar2(value*2);
var response = externalLibraryBar3(operation);
return response;
}
How to write a test for internalFoo1() so unit test will cover all its code lines, as well internalFoo2() one.

Ideally you shouldn't be testing external libraries, it's not your job.
In this case, you could just use a spy and see if that library has been called. http://jasmine.github.io/2.2/introduction.html#section-Spies
e.g. taken from Jasmine documentation:
describe("A spy, when configured with an alternate implementation", function() {
var foo, bar, fetchedBar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};
spyOn(foo, "getBar").and.callFake(function() {
return 1001;
});
foo.setBar(123);
fetchedBar = foo.getBar();
});
it("tracks that the spy was called", function() {
expect(foo.getBar).toHaveBeenCalled();
});
it("should not affect other functions", function() {
expect(bar).toEqual(123);
});
it("when called returns the requested value", function() {
expect(fetchedBar).toEqual(1001);
});
});

Related

Stubbing and/or spying on an optional global function: Sinon, mocha & chai

I have a method that checks whether or not a global function is defined (it may or may not be available, depends on each client's request). If it is defined, it will call it with the appropriate data. If not, it will fail silently. That's the desired behavior.
What I want to do is test it. Is there a way to mock and/or spy on libFunction so that I can ensure it's being called once with the correct data (the function here is very much simplified, there's some data processing that happens along the way).
Here's the method in question:
function sendData(data) {
let exists;
try {
// eslint-disable-next-line no-undef
if (libFunction) exists = true;
} catch (e) {
exists = false;
}
if (exists) {
// eslint-disable-next-line no-undef
libFunction(data);
}
}
I've tried defining libFunction in my tests and then stubbing that, but that doesn't do what I want:
describe('sendEvent', function () {
function libFunction(data) {
console.log('hi', data);
}
it('should call libFunction once', function () {
var stub = sinon.stub(libFunction);
var data = "testing";
sendEvent(data);
expect(stub.called).to.be.true;
});
});
This test does not pass, however: AssertionError: expected undefined to be true
I've tried something similar with a spy:
describe('sendEvent', function () {
function libFunction(data) {
console.log('hi', data);
}
it('should call libFunction once', function () {
var spy = sinon.spy(libFunction);
var data = "testing";
sendEvent(data);
expect(spy.called).to.be.true;
});
});
This also fails: AssertionError: expected false to be true
Is there a way to do this?
FWIW, I came across this question while trying to solve an issue for stubbing a global method in Node. In my case, this worked (my example uses Sinon.sandbox, but "regular" Sinon.spy should work, as well):
const encodeSpy = sandbox.spy(global, "encodeURIComponent");
// later...
Sinon.assert.calledWith(encodeSpy, {expectedParamValue});

Angular JS Unit testing: how to test function in service that doesn't return

I'm sorry for possible duplicate, I didn't find any solution for my problem.
I'm need to write tests for a service in Angular JS app.
So I have one main function that returns and uses as an external method. And a couple help functions for external. So how can I call and test things inside help functions (subFunc)?
service.js
function TestService() {
return {
mainFunc: mainFunc
};
funcion mainFunc() {
//do some and call subFunc()
subFunc(a)
}
function subFunc(a) {
if (a === 1) {
// ... magic 1
return true;
} else {
// ... magic 2
return false;
}
}
}
})()
service.spec.js
describe('Test Service', function() {
beforeEach(module('TestService'));
var TestService;
beforeEach(inject(function($injector) {
TestService = $injector.get('TestService');
}));
it('should return true if subFunc called with 1', function () {
// ....
});
})
Rewrite TestService to expose subFunc so that you can stub it with a spy and track whether it was called.
That is one of the reasons you write tests before and while writing the code: they will influence the style of the code so as to make it easier to test. In this case subFunc has to be externally visible to allow easy testing so make it visible.

Runtime unit testing in JavaScript

I've been testing JavaScript code using unit testing frameworks like jasmine and Qunit. But all these testing framework works only at load time, but I want to initiate the test cases at run time, for instance I want to test an object's value on a button click like below test case in Jasmine,
function btnClick() {
var temp++;
describe("Test Suite Inside Button Click", function () {
it("To test true value", function () {
expect(temp).not.toEqual(-1);
});
});
};
How to run the test cases dynamically ?
Here is how you do it.
Invoke the jasmineEnv at run time and run the test
Note that I'm clearing out the reporter div just to clean up the
output- which may not to necessary in your case.
My setTimeout is only to load the div onto the page
See it in action here
var testFunc = function() {
//this is optional- I'm just clearing the reporter out to run it on fiddle.
$('.jasmine_html-reporter').remove();
var jasmineEnv = jasmine.getEnv();
describe('test', function() {
it('sample test', function() {
console.log('test ran');
expect(true).toBe(true);
});
});
jasmineEnv.execute()
}
setTimeout(function() {
$('#myElement').click(testFunc);
}, 0);

How to convert nodejs based unit tests to native browser based unit tests?

Note, related How to test if jQuery 3.0 beta is Promises/A+ compatible in browser?
For example, at promises-tests one of the tests is found at promises-tests/lib/tests/2.1.2.js
"use strict";
var assert = require("assert");
var testFulfilled = require("./helpers/testThreeCases").testFulfilled;
var adapter = global.adapter;
var deferred = adapter.deferred;
var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it
describe("2.1.2.1: When fulfilled, a promise: must not transition to any other state.", function () {
testFulfilled(dummy, function (promise, done) {
var onFulfilledCalled = false;
promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
setTimeout(done, 100);
});
specify("trying to fulfill then immediately reject", function (done) {
var d = deferred();
var onFulfilledCalled = false;
d.promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
d.resolve(dummy);
d.reject(dummy);
setTimeout(done, 100);
});
specify("trying to fulfill then reject, delayed", function (done) {
var d = deferred();
var onFulfilledCalled = false;
d.promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
setTimeout(function () {
d.resolve(dummy);
d.reject(dummy);
}, 50);
setTimeout(done, 100);
});
specify("trying to fulfill immediately then reject delayed", function (done) {
var d = deferred();
var onFulfilledCalled = false;
d.promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
d.resolve(dummy);
setTimeout(function () {
d.reject(dummy);
}, 50);
setTimeout(done, 100);
});
});
Requirement:
The ability to run the test in the browser without reliance on node.js , a server, or installing libraries?
Question:
How can this test be converted to a version using native methods available at browsers, for example Console methods; that is, substitution of console.assert() or other native method available at window for describe() and specify()?
I still think this is a terrible idea, but here's a (trivial) example:
var assert = {};
assert.equals = function(expected, value, msg) {
var message = msg || "Test ";
try {
expected == value;
console.log(message, "passed");
catch (e) {
console.log(message, "failed", e);
}
};
Now repeat for >, <, ===, ranges, exceptions when they're expected, specific exceptions (e.g. TypeError, SyntaxError), typeof, instanceof, other types (built-ins, constructed), problematic values like NaN and null, etc.
NOTE: console.assert
There are some problems with console.assert. First, its non-standard and may not behave identically across platforms. Second, there is, AFAIK, no good way to abstract it: you'd end up doing just as much work as the above solution using console.log unless you use eval and string arguments:
function assert(str, msg) {
try {
console.assert(eval(str), msg);
catch (e) {
console.log(msg, " failed:", e);
}
}
assert("3 === 4", "Three equals four"); // logs the assertion failure.
Needless to say, I do not recommend this, as manually constructing the strings is error-prone, eval (even though safe in this case) is a notorious perf killer, and not using eval means using a parser and bam we're right back to the library bit.
Seriously, as you go down this road you will think of more and more things you want to be in there (see my list above) and you will realize you are writing a library when you could have used one.
UPDATE
Per your question in the comments below, many testing libraries (e.g. mocha, jasmine) use a format like this:
wrapper('name of the thing being tested', function(done) {
innerWrapper('description of the test', function(done) {
assert(someAssertion);
});
innerWrapper('some other test for that function', function(done) {
assert(somethingElse);
someAsyncFunction().then(function(value) {
assert(somethingAboutValue);
done();
});
});
});
The 'wrapper', 'innerWrapper', and 'assert' are added by including the testing library in the code or running the command (for cli) i.e. 'mocha tests.js' instead of 'node tests.js'. The setup may or may not use the inner functions to specify subtests. 'Done' is an argument to the callback that can be used to signal the end of an async test.
QUnit is a little simpler in its API, but not too far off.
UPDATE 2
Those for the most part are names for the same things: functions that wrap a test condition to make sure that correct messages are logged, or exceptions caught, or async gets a chance to finish. The assert tests the actual condition to be evaluated. The adapter reference means wrapping the jQuery deferred constructor to match the API in the spec.

Testing if a Jasmine Test Fails

I'm trying to write a plugin for Jasmine that allows you to return a promise from a spec and will pass or fail that spec depending on whether or not the promise is fulfilled or rejected.
Of course, I want to write tests to make sure that my plugin works correctly, and to be thorough, I need to make sure that tests fail when the promise is rejected... so how do I make a test pass when I need to make sure that a test "successfully fails"?
After a conversation with the developers who work on Jasmine, we've come up with this:
var FAILED = 'failed'
var PASSED = 'passed'
describe('My Test Suite', function () {
var env
beforeEach(function () {
// Create a secondary Jasmine environment to run your sub-specs in
env = new jasmine.Env()
})
it('should work synchronously', function () {
var spec
// use the methods on `env` rather than the global ones for sub-specs
// (describe, it, expect, beforeEach, etc)
env.describe('faux suite', function () {
spec = env.it('faux test', function (done) {
env.expect(true).toBe(true)
})
})
// this will fire off the specs in the secondary environment
env.execute()
// put your expectations here if the sub-spec is synchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
})
// don't forget the `done` argument for asynchronous specs
it('should work asynchronously', function (done) {
var spec
// use the methods on `env` rather than the global ones.
env.describe('faux suite', function () {
// `it` returns a spec object that we can use later
spec = env.it('faux test', function (done) {
Promise.reject("FAIL").then(done)
})
})
// this allows us to run code after we know the spec has finished
env.addReporter({jasmineDone: function() {
// put your expectations in here if the sub-spec is asynchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
// this is how Jasmine knows you've completed something asynchronous
// you need to add it as an argument to the main `it` call above
done()
}})
// this will fire off the specs in the secondary environment
env.execute()
})
})
Going off Joe's answer, I moved the fake test context into a single function. Since the code under test is making use of jasmine expectations, I load the inner Env into jasmine.currentEnv_ and call it explicitly with jasmine.currentEnv_.expect(). Note that currentEnv_ is an internal variable set by jasmine itself, so I can't guarantee that this won't be broken in a future jasmine version.
function internalTest(testFunc) {
var outerEnvironment = jasmine.currentEnv_;
var env = new jasmine.Env();
jasmine.currentEnv_ = env;
var spec;
env.describe("fake suite", function () {
spec = env.it("fake test", function () {
func();
});
});
env.execute();
jasmine.currentEnv_ = outerEnvironment;
return spec.result;
}
Then each test looks like
it("does something", function () {
//Arrange
//Act
var result = internalTest(function () {
//Perform action
});
//Assert
expect(result.status).toBe("failed"); //Or "success"
expect(result.failedExpectations.length).toBe(1);
expect(result.failedExpectations[0].message).toBe("My expected error message");
});

Categories

Resources