I'm very new to unit test and want to use Jasmine for unit testing mouse events, something like
$('.el').mousedown(function() {
$(this)[0].dragging = true;
});
$('.el').mousemove(function() {
if ( $(this)[0].dragging ) {
$(this).addClass("dragging");
}
});
$('.el').mouseup(function() {
$(this)[0].dragging = false;
$(this).removeClass("dragging");
});
Here's an example of how it might work.
My question is how to do the unit test for each function here.
The functions ought to be named functions in $scope. Then you could simply call the function in your test and verify that the element had the class removed/added as expected.
Related
I am pretty new in Jasmine test.
Lett say I have this sample.js file:
(function() {
function myFunction() {
do some thing and then make a call to MyOtherFunciton
}
myOtherFunciton(p1, p2, p3) {
//do some stuff in here
}
module.exports = {
myOtherFunciton,
myFunction
}
})();
Now I have this jasmine test does the following
const mySampleFile = require(./sample.js);
spyOn(mySampleFile, "myOtherFunciton").and.callFack(()=>{
});
mySampleFile.myFunction();
expect(mySampleFile.myOtherFunciton).toHaveBeenCalled();
The problem I am experiencing is that it makes a call to real myOtherFunciton function but not the mocked one. Why is that ?
It's a function scope problem you are running into. The function myOtherFunciton() called from within myFunction() is not the same as mySampleFile.myOtherFunciton(), as you found out. To fix will require a slight refactor of your original code (don't you love it when testing exposes such things?). I added some console.logs below just to be clear where the execution context is going during testing.
Suggested refactor:
(function() {
exports.myFunction = function() {
// do some thing and then make a call to MyOtherFunciton
console.log('inside myFunction');
exports.myOtherFunciton('a', 'b', 'c'); // scoped to exports, now can be mocked
// myOtherFunciton('a', 'b', 'c'); // <-- don't call it like this: scoped within IIFE
}
exports.myOtherFunciton = function(p1, p2, p3) {
console.log('inside original myOtherFunciton');
//do some stuff in here
}
// module.exports = {
// myOtherFunciton,
// myFunction
// }
})();
Here is a working StackBlitz showing the test now passing. Click on 'console' below the Jasmine test window to see the output.
I hope this helps.
In my journey towards TDD, I am using Mocha, chai and sinon.
There certainly is a learning curve there.
My goal is to write a test to verify that method4 was executed. How do I achieve that ?
//MyData.js
class MyData {
constructor(input) {
this._runMethod4 = input; //true or false
this.underProcessing = this.init();
}
method1() { return this.method2() }
method2() {
if (this._runMethod4) {
return this.method4();
} else {
return this.method3();
}
method4(){
return thirdPartyAPI.getData();
}
method3(){
return someAPI.fetchData();
}
init(){
return this.method1();
}
}
MyData.spec.js
describe('MyData', () => {
it('should execute method 4', function() {
let foo = new MyData(true);
foo.underProcessing.then(()=>{
// How do I verify that method4 was executed ??
expect(foo.method4.callCount).to.equal(1);
});
});
})
Here's an example:
const expect = require('chai').expect;
const sinon = require('sinon');
const sinonTest = require('sinon-test');
sinon.test = sinonTest.configureTest(sinon);
sinon.testCase = sinonTest.configureTestCase(sinon);
describe("MyData", () => {
it("should execute method 4", sinon.test(function() {
let spy = this.spy(MyData.prototype, 'method4');
let foo = new MyData(true);
return foo.underProcessing.then(() => {
expect(spy.callCount).to.equal(1);
});
}));
});
As an additional requirement, I added sinon-test because it's really useful to help clean up spies/stubs after the test has run.
The main feature is this line:
let spy = this.spy(MyData.prototype, 'method4');
This replaces MyData.prototype.method4 by a Sinon spy, which is a pass-through function (so it calls the original) that will record how exactly it was called, how often, with what arguments, etc. You need to do this before the instance is created, because otherwise you're too late (the method might already have been called through the chain of method calls that starts with this.init() in the constructor).
If you want to use a stub instead, which is not pass-through (so it won't call the original method), you can do that as well:
let spy = this.stub(MyData.prototype, 'method4').returns(Promise.resolve('yay'));
So instead of calling thirdPartyAPI.getData() and returning its result, method4 will now return a promise resolved with the value yay.
The remaining code should speak for itself, with one caveat: an explicit return in front of foo.underProcessing.then(...).
I assume that foo.underProcessing is a promise, so it's asynchronous, which means that your test should be asynchronous as well. Since Mocha supports promises, when you return a promise from a test, Mocha will know how to deal with it properly (there's an alternative method of making an asynchronous test with Mocha, involving callback functions, but when you're testing promise-based code you shouldn't really use those since it's easy to run into timeouts or swallowed exceptions).
We can define the following test:
spyOn(x, 'funk').andReturn(true);
If we then wanted to define:
spyOn(x, 'funk').andReturn(false);
We would get an error saying that funk had already been spied on.
How could we effectively unspy so that could respy with the new return value?
You can re-train spies:
//--- CODE --------------------------
x = {
funk: function() {
return 1;
}
}
// --- SPECS -------------------------
describe('test x', function () {
it("trains spies", function () {
spyOn(x, 'funk');
x.funk.andReturn('a');
expect(x.funk()).toBe('a');
x.funk.andCallThrough();
expect(x.funk()).toBe(1);
});
});
See fiddle here - http://jsfiddle.net/eitanp461/88kvnzx3/
Yes it's possible to unspy.
As the spy is just a function that replaces whatever function you want to spy on, you can replace it again with another function.
Two sensible ways of doing this are as follows - first, just add an empty function, Jasmine will have all it needs for a new spy:
x.funk = function() {}
Or, if the functionality of original function is important, just store it in a value like so:
var tempFunk = x.funk
/* do stuff */
x.funk = tempFunk
I have some tests that fail in PhantomJS but not other browsers.
I'd like these tests to be ignored when run with PhantomJS in my watch task (so new browser windows don't take focus and perf is a bit faster), but in my standard test task and my CI pipeline, I want all the tests to run in Chrome, Firefox, etc...
I've considered a file-naming convention like foo.spec.dont-use-phantom.js and excluding those in my Karma config, but this means that I will have to separate out the individual tests that are failing into their own files, separating them from their logical describe blocks and having more files with weird naming conventions would generally suck.
In short:
Is there a way I can extend Jasmine and/or Karma and somehow annotate individual tests to only run with certain configurations?
Jasmine supports a pending() function.
If you call pending() anywhere in the spec body, no matter the expectations, the spec will be marked pending.
You can call pending() directly in test, or in some other function called from test.
function skipIfCondition() {
pending();
}
function someSkipCheck() {
return true;
}
describe("test", function() {
it("call pending directly by condition", function() {
if (someSkipCheck()) {
pending();
}
expect(1).toBe(2);
});
it("call conditionally skip function", function() {
skipIfCondition();
expect(1).toBe(3);
});
it("is executed", function() {
expect(1).toBe(1);
});
});
working example here: http://plnkr.co/edit/JZtAKALK9wi5PdIkbw8r?p=preview
I think it is purest solution. In test results you can see count of finished and skipped tests.
The most simple solution that I see is to override global functions describe and it to make them accept third optional argument, which has to be a boolean or a function returning a boolean - to tell whether or not current suite/spec should be executed. When overriding we should check if this third optional arguments resolves to true, and if it does, then we call xdescribe/xit (or ddescribe/iit depending on Jasmine version), which are Jasmine's methods to skip suite/spec, istead of original describe/it. This block has to be executed before the tests, but after Jasmine is included to the page. In Karma just move this code to a file and include it before test files in karma.conf.js. Here is the code:
(function (global) {
// save references to original methods
var _super = {
describe: global.describe,
it: global.it
};
// override, take third optional "disable"
global.describe = function (name, fn, disable) {
var disabled = disable;
if (typeof disable === 'function') {
disabled = disable();
}
// if should be disabled - call "xdescribe" (or "ddescribe")
if (disable) {
return global.xdescribe.apply(this, arguments);
}
// otherwise call original "describe"
return _super.describe.apply(this, arguments);
};
// override, take third optional "disable"
global.it = function (name, fn, disable) {
var disabled = disable;
if (typeof disable === 'function') {
disabled = disable();
}
// if should be disabled - call "xit" (or "iit")
if (disable) {
return global.xit.apply(this, arguments);
}
// otherwise call original "it"
return _super.it.apply(this, arguments);
};
}(window));
And usage example:
describe('foo', function () {
it('should foo 1 ', function () {
expect(true).toBe(true);
});
it('should foo 2', function () {
expect(true).toBe(true);
});
}, true); // disable suite
describe('bar', function () {
it('should bar 1 ', function () {
expect(true).toBe(true);
});
it('should bar 2', function () {
expect(true).toBe(true);
}, function () {
return true; // disable spec
});
});
See a working example here
I've also stumbled upon this issue where the idea was to add a chain method .when() for describe and it, which will do pretty much the same I've described above. It may look nicer but is a bit harder to implement.
describe('foo', function () {
it('bar', function () {
// ...
}).when(anything);
}).when(something);
If you are really interested in this second approach, I'll be happy to play with it a little bit more and try to implement chain .when().
Update:
Jasmine uses third argument as a timeout option (see docs), so my code sample is replacing this feature, which is not ok. I like #milanlempera and #MarcoCI answers better, mine seems kinda hacky and not intuitive. I'll try to update my solution anyways soon not to break compatibilty with Jasmine default features.
I can share my experience with this.
In our environment we have several tests running with different browsers and different technologies.
In order to run always the same suites on all the platforms and browsers we have a helper file loaded in karma (helper.js) with some feature detection functions loaded globally.
I.e.
function isFullScreenSupported(){
// run some feature detection code here
}
You can use also Modernizr for this as well.
In our tests then we wrap things in if/else blocks like the following:
it('should work with fullscreen', function(){
if(isFullScreenSupported()){
// run the test
}
// don't do anything otherwise
});
or for an async test
it('should work with fullscreen', function(done){
if(isFullScreenSupported()){
// run the test
...
done();
} else {
done();
}
});
While it's a bit verbose it will save you time for the kind of scenario you're facing.
In some cases you can use user agent sniffing to detect a particular browser type or version - I know it is bad practice but sometimes there's effectively no other way.
Try this. I am using this solution in my projects.
it('should do something', function () {
if (!/PhantomJS/.test(window.navigator.userAgent)) {
expect(true).to.be.true;
}
});
This will not run this particular test in PhantomJS, but will run it in other browsers.
Just rename the tests that you want to disable from it(...) to xit(...)
function xit: A temporarily disabled it. The spec will report as
pending and will not be executed.
There are plenty of documents that show how to add a matcher to a Jasmine spec (here, for example).
Has anyone found a way to add matchers to the whole environment; I'm wanting to create a set of useful matchers to be called by any and all tests, without copypasta all over my specs.
Currently working to reverse engineer the source, but would prefer a tried and true method, if one exists.
Sure, you just call beforeEach() without any spec scoping at all, and add matchers there.
This would globally add a toBeOfType matcher.
beforeEach(function() {
var matchers = {
toBeOfType: function(typeString) {
return typeof this.actual == typeString;
}
};
this.addMatchers(matchers);
});
describe('Thing', function() {
// matchers available here.
});
I've made a file named spec_helper.js full of things like custom matchers that I just need to load onto the page before I run the rest of the spec suite.
Here's one for jasmine 2.0+:
beforeEach(function(){
jasmine.addMatchers({
toEqualData: function() {
return {
compare: function(actual, expected) {
return { pass: angular.equals(actual, expected) };
}
};
}
});
});
Note that this uses angular's angular.equals.
Edit: I didn't know it was an internal implementation that may be subjected to change. Use at your own risk.
jasmine.Expectation.addCoreMatchers(matchers)
Based on previous answers, I created the following setup for angular-cli. I also need an external module in my matcher (in this case moment.js)
Note In this example I added an equalityTester, but it should work with a customer matcher
Create a file src/spec_helper.ts with the following contents:
// Import module
import { Moment } from 'moment';
export function initSpecHelper() {
beforeEach(() => {
// Add your matcher
jasmine.addCustomEqualityTester((a: Moment, b: Moment) => {
if (typeof a.isSame === 'function') {
return a.isSame(b);
}
});
});
}
Then, in src/test.ts import the initSpecHelper() function add execute it. I placed it before Angular's TestBed init, wich seems to work just fine.
import { initSpecHelper } from './spec_helper';
//...
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// Init our own spec helper
initSpecHelper();
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
//...