- Notifications
You must be signed in to change notification settings - Fork0
Provides UI for testing frameworks such as mocha, jasmine and jest which allows to define lazy variables and subjects.
License
rinslow/bdd-lazy-var
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A maintained fork ofjspec.
describe('Suite',function(){varname;beforeEach(function(){name=getName();});afterEach(function(){name=null;});it('uses name variable',function(){expect(name).to.exist;});it('does not use name but anyway it is created in beforeEach',function(){expect(1).to.equal(1);});});
Because as soon as amount of your tests increase, this pattern became increasingly difficult.Sometimes you will find yourself jumping around spec files, trying to find out where a given variable was initially defined.Or even worst, you may run into subtle bugs due to clobbering variables with common names (e.g.model,view) within a given scope, failing to realize they had already been defined.Furthermore, declaration statements indescribe blocks will start looking something like:
varfirstVar,secondVar,thirdVar,fourthVar,fifthVar, ...,nthVar
This is ugly and hard to parse. Finally, you can sometimes run into flaky tests due to "leaks" - test-specific variables that were not properly cleaned up after each case.
In an attempt to address these issues, I had with my e2e tests, I decided to create this library, which allows to define suite specific variables in more elegant way.So the original code above looks something like this:
describe('Suite',()=>{def('name',()=>`John Doe${Math.random()}`);it('defines `name` variable',()=>{expect($name).to.exist});it('does not use name, so it is not created',()=>{expect(1).to.equal(1);});});
Switching over to this pattern has yielded a significant amount of benefits for us, including:
Because lazy vars are cleared after each test, we didn't have to worry about test pollution anymore. This helped ensure isolation between our tests, making them a lot more reliable.
Every time I see a$<variable> reference in my tests, I know where it's defined.That, coupled with removing exhaustivevar declarations indescribe blocks, have made even my largest tests clear and understandable.
Variables are instantiated only when referenced.That means if you don't use variable inside your test it won't be evaluated, making your tests to run faster.No useless instantiation any more!
Due to laziness we are able to compose variables. This allows to define more general varibles at the top level and more specific at the bottom:
describe('User',function(){subject('user',()=>newUser($props))describe('when user is "admin"',function(){def('props',()=>({role:'admin'}))it('can update articles',function(){// user is created with property role equal "admin"expect($user).to....})})describe('when user is "member"',function(){def('props',()=>({role:'member'}))it('cannot update articles',function(){// user is created with property role equal "member"expect($user).to....})})})
Very often you may find that some behavior repeats (e.g., when you implement Adapter pattern),and you would like to reuse tests for a different class or object.To do thisWiki of Mocha.js recommend to move your tests into separate function and call it whenever you need it.
I prefer to be more explicit in doing this, that's why created few helper methods:
sharedExamplesFor- defines a set of reusable tests. When you call this function, it just stores your testsincludeExamplesFor- runs previously defined examples in current context (i.e., in currentdescribe)itBehavesLike- runs defined examples in nested context (i.e., in nesteddescribe)
sharedExamplesFor defines shared examples in the scope of the currently defining suite.If you call this function outsidedescribe (orcontext) it defines shared examples globally.
WARNING: files containing shared examples must be loaded before the files thatuse them.
shared examples group included in two groups in one file
sharedExamplesFor('a collection',()=>{it('has three items',()=>{expect($subject.size).to.equal(3)})describe('#has',()=>{it('returns true with an item that is in the collection',()=>{expect($subject.has(7)).to.be.true})it('returns false with an item that is not in the collection',()=>{expect($subject.has(9)).to.be.false})})})describe('Set',()=>{subject(()=>newSet([1,2,7]))itBehavesLike('a collection')})describe('Map',()=>{subject(()=>newMap([[2,1],[7,5],[3,4]]))itBehavesLike('a collection')})
Passing parameters to a shared example group
sharedExamplesFor('a collection',(size,existingItem,nonExistingItem)=>{it('has three items',()=>{expect($subject.size).to.equal(size)})describe('#has',()=>{it('returns true with an item that is in the collection',()=>{expect($subject.has(existingItem)).to.be.true})it('returns false with an item that is not in the collection',()=>{expect($subject.has(nonExistingItem)).to.be.false})})})describe('Set',()=>{subject(()=>newSet([1,2,7]))itBehavesLike('a collection',3,2,10)})describe('Map',()=>{subject(()=>newMap([[2,1]]))itBehavesLike('a collection',1,2,3)})
Passing lazy vars to a shared example group
There are 2 ways how to pass lazy variables:
- all variables are inherited by nested contexts (i.e.,
describecalls),so you can rely on variable name, as it was done withsubjectin previous examples - you can pass variable definition using
get.variablehelper
sharedExamplesFor('a collection',(collection)=>{def('collection',collection)it('has three items',()=>{expect($collection.size).to.equal(1)})describe('#has',()=>{it('returns true with an item that is in the collection',()=>{expect($collection.has(7)).to.be.true})it('returns false with an item that is not in the collection',()=>{expect($collection.has(9)).to.be.false})})})describe('Set',()=>{subject(()=>newSet([7]))itBehavesLike('a collection',get.variable('subject'))})describe('Map',()=>{subject(()=>newMap([[2,1]]))itBehavesLike('a collection',get.variable('subject'))})
Very often we want to declare several test cases which tests subject's field or subject's behavior.To do this quickly you can useits orit without message:
Shortcuts example
describe('Array',()=>{subject(()=>({items:[1,2,3],name:'John'}))its('items.length',()=>is.expected.to.equal(3))// i.e. expect($subject.items.length).to.equal(3)its('name',()=>is.expected.to.equal('John'))// i.e. expect($subject.name).to.equal('John')// i.e. expect($subject).to.have.property('items').which.has.length(3)it(()=>is.expected.to.have.property('items').which.has.length(3))})
Also it generates messages for you based on passed in function body. The example above reports:
Array ✓ is expected to have property('items') which has length(3) items.length ✓ is expected to equal(3) name ✓ is expected to equal('John')
Note: if you usemocha andchai make sure that definesglobal.expect = chai.expect, otherwiseis.expected will throw error thatcontext.expect isundefined.
npm install jspec --save-dev
Mocha.js
mocha -u jspec/global
SeeUsing Mocha programatically
constMocha=require('mocha');constmocha=newMocha({ui:'jspec/global'// jspec or jspec/getter});mocha.addFile(...)mocha.run(...)// !!! Important the next code should be written in a separate file// later you can either use `get` and `def` as global functions// or export them from corresponding moduleconst{ get, def}=require('jspec/global');describe('Test',()=>{// ...})
Note requireskarma-mocha^1.1.1
So, inkarma.config.js it looks like this:
module.exports=function(config){config.set({// ....client:{mocha:{ui:'jspec/global',require:[require.resolve('jspec/global')]}}});}
Jasmine.js
jasmine --helper=node_modules/jspec/global.js
or usingspec/spec_helper.js
require('jspec/global');// ... other helper stuff
and then
jasmine --helper=spec/*_helper.jsWhen you want programatically run jasmine
require('jasmine-core');// !!! Important the next code should be written in a separate file// later you can either use `get` and `def` as global functions// or export them from corresponding moduleconst{ get, def}=require('jspec/global');describe('Test',()=>{// ...})
So, inkarma.config.js it looks like this:
module.exports=function(config){config.set({// ....files:['node_modules/jspec/global.js',// ... your specs here]});}
Jest
Use Jest as usually if you exportget anddef from corresponding module
jestIn case you want to use globalget anddef
jest --setupTestFrameworkScriptFile jspec/global
// later you can either use `get` and `def` as global functions// or export them from relative moduleconst{ get, def}=require('jspec/global');
jspec provides 3 different dialects:
- access variables by referencing
$<variableName>(the recommended one, available by requiringjspec/global) - access variables by referencing
get.<variableName>(more strict, available by requiringjspec/getter) - access variables by referencing
get('<variableName>')(the most strict and less readable way, available by requiringjspec)
All are bundled as UMD versions. Each dialect is compiled in a separate file and should be required or provided for testing framework.
In accordance with Rspec's DDL,context,xcontext, andfcontext have been aliased to their relateddescribe commands for both the Jest and Jasmine testing libraries. Mocha's BDD interface already provides this keyword.
- lazy instantiation, allows variable composition
- automatically cleaned after each test
- accessible inside
before/beforeAll,after/afterAllcallbacks - named
subjects to be more explicit - ability to shadow parent's variable
- variable inheritance with access to parent variables
- supports typescript
- ability to make instantiation eager (similiar to RSpec's
let!)
For more information, readthe article on Medium.
It's also possible to usejspec with TypeScript. The best integrated dialects areget andgetter. To do so, you need either include corresponding definitions in yourtsconfig.json or use ES6 module system.
tsconfig.json
{"compilerOptions":{"module":"commonjs","removeComments":true,"preserveConstEnums":true,"sourceMap":true},"include":["src/**/*","node_modules/jspec/index.d.ts"// for `get('<variableName>')` syntax// or"node_modules/jspec/getter.d.ts"// for `get.<variableName>` syntax]}
ES6 module system
import{get,def}from'jspec'// orimport{get,def}from'jspec/getter'describe('My Test',()=>{// ....})
In this case TypeScript loads corresponding declarations automatically
It's a bit harder to work withglobal dialect. It creates global getters on the fly, so there is no way to let TypeScript know something about these variables, thus you need todeclare them manually.
TypeScript and global dialect
import{def}from'jspec/global'describe('My Test',()=>{declarelet$value:number// <-- need to place this declarations manuallydef('value',()=>5)it('equals 5',()=>{expect($value).to.equal(5)})})
As with other dialects you can either useimport statements to load typings automatically or add them manually intsconfig.json
Test with subject
describe('Array',()=>{subject(()=>[1,2,3]);it('has 3 elements by default',()=>{expect($subject).to.have.length(3);});});
Named subject
describe('Array',()=>{subject('collection',()=>[1,2,3]);it('has 3 elements by default',()=>{expect($subject).to.equal($collection);expect($collection).to.have.length(3);});});
`beforeEach` and redefined subject
describe('Array',()=>{subject('collection',()=>[1,2,3]);beforeEach(()=>{// this beforeEach is executed for tests of suite with subject equal [1, 2, 3]// and for nested describe with subject being []$subject.push(4);});it('has 3 elements by default',()=>{expect($subject).to.equal($collection);expect($collection).to.have.length(3);});describe('when empty',()=>{subject(()=>[]);it('has 1 element',()=>{expect($subject).not.to.equal($collection);expect($collection).to.deep.equal([4]);});});});
Access parent variable in child variable definition
describe('Array',()=>{subject('collection',()=>[1,2,3]);it('has 3 elements by default',()=>{expect($subject).to.equal($collection);expect($collection).to.have.length(3);});describe('when empty',()=>{subject(()=>{// in this definition `$subject` references parent $subject (i.e., `$collection` variable)return$subject.concat([4,5]);});it('is properly uses parent subject',()=>{expect($subject).not.to.equal($collection);expect($collection).to.deep.equal([1,2,3,4,5]);});});});
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines forcontributing
About
Provides UI for testing frameworks such as mocha, jasmine and jest which allows to define lazy variables and subjects.
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Languages
- JavaScript100.0%