Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
bdd-lazy-var
Advanced tools
Provides "ui" for testing frameworks such as mocha/jasmine which allows to define lazy variables and subjects
Provides "ui" for testing frameworks such as mocha, jasmine and jest which allows to define lazy variables and subjects.
describe('Suite', function() {
var name;
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 in describe
blocks will start looking something like:
var firstVar, 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 exhaustive var
declarations in describe
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', () => new User($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 this Wiki 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 current describe
)itBehavesLike
- runs defined examples in nested context (i.e., in nested describe
)sharedExamplesFor
defines shared examples in the scope of the currently defining suite.
If you call this function outside describe
(or context
) it defines shared examples globally.
WARNING: files containing shared examples must be loaded before the files that use them.
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(() => new Set([1, 2, 7]))
itBehavesLike('a collection')
})
describe('Map', () => {
subject(() => new Map([[2, 1], [7, 5], [3, 4]]))
itBehavesLike('a collection')
})
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(() => new Set([1, 2, 7]))
itBehavesLike('a collection', 3, 2, 10)
})
describe('Map', () => {
subject(() => new Map([[2, 1]]))
itBehavesLike('a collection', 1, 2, 3)
})
There are 2 ways how to pass lazy variables:
describe
calls),
so you can rely on variable name, as it was done with subject
in previous examplesget.variable
helpersharedExamplesFor('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(() => new Set([7]))
itBehavesLike('a collection', get.variable('subject'))
})
describe('Map', () => {
subject(() => new Map([[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 use its
or it
without message:
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 use mocha
and chai
make sure that defines global.expect = chai.expect
, otherwise is.expected
will throw error that context.expect
is undefined
.
npm install bdd-lazy-var --save-dev
mocha -u bdd-lazy-var/global
See Using Mocha programatically
const Mocha = require('mocha');
const mocha = new Mocha({
ui: 'bdd-lazy-var/global' // bdd-lazy-var or bdd-lazy-var/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 module
const { get, def } = require('bdd-lazy-var/global');
describe('Test', () => {
// ...
})
Note requires karma-mocha
^1.1.1
So, in karma.config.js
it looks like this:
module.exports = function(config) {
config.set({
// ....
client: {
mocha: {
ui: 'bdd-lazy-var/global',
require: [require.resolve('bdd-lazy-var/global')]
}
}
});
}
jasmine --helper=node_modules/bdd-lazy-var/global.js
or using spec/spec_helper.js
require('bdd-lazy-var/global');
// ... other helper stuff
and then
jasmine --helper=spec/*_helper.js
When 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 module
const { get, def } = require('bdd-lazy-var/global');
describe('Test', () => {
// ...
})
So, in karma.config.js
it looks like this:
module.exports = function(config) {
config.set({
// ....
files: [
'node_modules/bdd-lazy-var/global.js',
// ... your specs here
]
});
}
Use Jest as usually if you export get
and def
from corresponding module
jest
In case you want to use global get
and def
jest --setupTestFrameworkScriptFile bdd-lazy-var/global
// later you can either use `get` and `def` as global functions
// or export them from relative module
const { get, def } = require('bdd-lazy-var/global');
bdd-lazy-var
provides 3 different dialects:
$<variableName>
(the recommended one, available by requiring bdd-lazy-var/global
)get.<variableName>
(more strict, available by requiring bdd-lazy-var/getter
)get('<variableName>')
(the most strict and less readable way, available by requiring bdd-lazy-var
)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
, and fcontext
have been aliased to their related describe
commands for both the Jest and Jasmine testing libraries. Mocha's BDD interface already provides this keyword.
before/beforeAll
, after/afterAll
callbackssubject
s to be more explicitFor more information, read the article on Medium.
It's also possible to use bdd-lazy-var
with TypeScript. The best integrated dialects are get
and getter
. To do so, you need either include corresponding definitions in your tsconfig.json or use ES6 module system.
{
"compilerOptions": {
"module": "commonjs",
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true
},
"include": [
"src/**/*",
"node_modules/bdd-lazy-var/index.d.ts" // for `get('<variableName>')` syntax
// or
"node_modules/bdd-lazy-var/getter.d.ts" // for `get.<variableName>` syntax
]
}
import { get, def } from 'bdd-lazy-var'
// or
import { get, def } from 'bdd-lazy-var/getter'
describe('My Test', () => {
// ....
})
In this case TypeScript loads corresponding declarations automatically
It's a bit harder to work with global
dialect. It creates global getters on the fly, so there is no way to let TypeScript know something about these variables, thus you need to declare
them manually.
import { def } from 'bdd-lazy-var/global'
describe('My Test', () => {
declare let $value: number // <-- need to place this declarations manually
def('value', () => 5)
it('equals 5', () => {
expect($value).to.equal(5)
})
})
As with other dialects you can either use import
statements to load typings automatically or add them manually in tsconfig.json
describe('Array', () => {
subject(() => [1, 2, 3]);
it('has 3 elements by default', () => {
expect($subject).to.have.length(3);
});
});
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('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]);
});
});
});
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 for contributing
FAQs
Provides "ui" for testing frameworks such as mocha/jasmine which allows to define lazy variables and subjects
We found that bdd-lazy-var demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.