[ACCEPTED]-What's a good way to reuse test code using Jasmine?-jasmine
Here is an article by a guy at Pivotal Labs 3 that goes into detail about how to wrap 2 a describe call:
DRYing up Jasmine Specs with Shared Behavior
Snippet from the article 1 that shows part of the wrapper function:
function sharedBehaviorForGameOf(context) {
describe("(shared)", function() {
var ball, game;
beforeEach(function() {
ball = context.ball;
game = context.game;
});
});
}
I'm not sure how @starmer's solution works. As 7 I mentioned in the comment, when I use his 6 code, context
is always undefined.
Instead what 5 you have to do (as mentioned by @moefinley) is 4 to pass in a reference to a constructor 3 function instead. I've written a blog post that outlines this 2 approach using an example. Here's the essence 1 of it:
describe('service interface', function(){
function createInstance(){
return /* code to create a new service or pass in an existing reference */
}
executeSharedTests(createInstance);
});
function executeSharedTests(createInstanceFn){
describe('when adding a new menu entry', function(){
var subjectUnderTest;
beforeEach(function(){
//create an instance by invoking the constructor function
subjectUnderTest = createInstanceFn();
});
it('should allow to add new menu entries', function(){
/* assertion code here, verifying subjectUnderTest works properly */
});
});
}
There's a nice article on thoughbot's website: https://robots.thoughtbot.com/jasmine-and-shared-examples
Here's 2 a brief sample:
appNamespace.jasmine.sharedExamples = {
"rectangle": function() {
it("has four sides", function() {
expect(this.subject.sides).toEqual(4);
});
},
};
And with some underscore 1 functions to define itShouldBehaveLike
window.itShouldBehaveLike = function() {
var exampleName = _.first(arguments),
exampleArguments = _.select(_.rest(arguments), function(arg) { return !_.isFunction(arg); }),
innerBlock = _.detect(arguments, function(arg) { return _.isFunction(arg); }),
exampleGroup = appNamespace.jasmine.sharedExamples[exampleName];
if(exampleGroup) {
return describe(exampleName, function() {
exampleGroup.apply(this, exampleArguments);
if(innerBlock) { innerBlock(); }
});
} else {
return it("cannot find shared behavior: '" + exampleName + "'", function() {
expect(false).toEqual(true);
});
}
};
Let me summarize it with working example
describe('test', function () {
beforeEach(function () {
this.shared = 1;
});
it('should test shared', function () {
expect(this.shared).toBe(1);
});
testShared();
});
function testShared() {
it('should test in function', function() {
expect(this.shared).toBe(1);
});
}
The 11 crucial parts here are this keyword to pass 10 context and because of this we have to use 9 "normal" functions (another crucial part).
For production code 8 I would probably use normal function only 7 in beforeEach
to pass/extract context but keep to 6 use arrow-function in specs for brevity.
Passing 5 context as parameter wouldn't work because 4 normally we define context in beforeEach
block wich 3 invoked after.
Having describe
section seems not 2 important, but still welcome for better 1 structure
This is similar to starmer's answer, but 6 after working through it I found some differences 5 to point out. The downside is that if the 4 spec fails you just see 'should adhere to 3 common saving specifications' in the Jasmine 2 report. The stack trace is the only way 1 to find where it failed.
// common specs to execute
self.executeCommonSpecifications = function (vm) {
// I found having the describe( wrapper here doesn't work
self.shouldCallTheDisplayModelsSaveMethod(vm);
}
self.shouldCallTheDisplaysSaveMethod = function (vm) {
expect(vm.save.calls.count()).toBe(1);
};
// spec add an it so that the beforeEach is called before calling this
beforeEach(function(){
// this gets called if wrapped in the it
vm.saveChanges();
}
it('should adhere to common saving specifications', function () {
executeSavingDisplaysCommonSpecifications(vm);
});
This is the approach I have taken, inspired 11 by this article:
https://gist.github.com/traviskaufman/11131303
which is based on Jasmine 10 own documentation:
http://jasmine.github.io/2.0/introduction.html#section-The_%3Ccode%3Ethis%3C/code%3E_keyword
By setting shared dependencies 9 as properties of beforeEach
function prototype, you 8 can extend beforeEach
to make this dependencies available 7 via this
.
Example:
describe('A suite', function() {
// Shared setup for nested suites
beforeEach(function() {
// For the sake of simplicity this is just a string
// but it could be anything
this.sharedDependency = 'Some dependency';
});
describe('A nested suite', function() {
var dependency;
beforeEach(function() {
// This works!
dependency = this.sharedDependency;
});
it('Dependency should be defined', function() {
expect(dependency).toBeDefined();
});
});
describe('Check if string split method works', function() {
var splitToArray;
beforeEach(function() {
splitToArray = this.sharedDependency.split();
});
it('Some other test', function() { ... });
});
});
I know my example is kind of 6 useless but it should serve its purpose 5 as code example.
Of course this is just one 4 of the many things you could do to achieve 3 what you say, I'm sure that more complex 2 design patterns may be applied on top or 1 aside to this one.
Hope it helps!
Here is a simpler solution. Declare a variable 4 function and use it, without using the this 3 keyword or context:
describe("Test Suit", function ()
{
var TestCommonFunction = function(inputObjects)
{
//common code here or return objects and functions here etc
};
it("Should do x and y", function()
{
//Prepare someInputObjects
TestCommonFunction(someInputObjects);
//do the rest of the test or evaluation
});
});
You can also return an 2 object with more functions and call the 1 returned functions thereafter.
I wasn't hapy about having to instantiate 9 a whole new Component for my Angular tests 8 that were sharing logic on different function 7 calls (e.g. a private function called from 6 two separate public functions on the component). The 5 TestBed intantiates the component and doing 4 so by hand just to run shared tests seemed 3 dirty to me. I fixed my issue by avoiding 2 the component reference altogether and calling 1 functions by their name in the shared testsuite:
describe('firstFunction', () => {
itShouldExecuteSharedFunction('firstFunction');
});
describe('secondFunction', () => {
itShouldExecuteSharedFunction('secondFunction');
});
function itShouldExecuteSharedFunction(fnName: string) {
describe('sharedFunction', () => {
beforeEach(() => {...});
afterEach(() => {...});
it('should do something', () => {
component[fnName]();
expect(...);
});
});
}
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.