
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
o-testing-toolbox
Advanced tools
Additional expectations for chai package and other utilities
Install the package with
npm install --save-dev o-testing-toolbox
For this extension to work first you need to install the following packages:
npm install --save-dev mocha
npm install --save-dev chai
First include the LetBeExtesion plugin for chai in your test file
const chai = require('chai')
const {expect} = require('chai')
const {LetBeExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
Then define a variable with
letBe.loginUrl = () => '/login'
(read it let loginUrl be '/login' like in math: let O be a not empty set)
If the value is a literal it is possible to use the shortcut
letBe.loginUrl = '/login'
Note that using the shortcut does not assign the value. It wraps it with an initializer. It also freezes the constant to avoid errors caused by secondary effects on the variable object. As a rule of thumb always use the first form.
letBe definitions can be anywhere in the test:
beforeEach( () => {
letBe.loginUrl = () => '/login'
})
describe('The login endpoint', () => {
...
})
describe('The login endpoint', () => {
beforeEach( () => {
letBe.loginUrl = () => '/login'
})
...
})
describe('The login endpoint', () => {
describe('returns 200', () => {
beforeEach( () => {
letBe.loginUrl = () => '/login'
})
})
...
})
describe('The login endpoint', () => {
it('returns 200', () => {
letBe.loginUrl = () => '/login'
})
...
})
Avoid defining letBe statements out of a beforeEach or before block
// Not good. This assignment is global and it's executed only once when the file is required
letBe.loginUrl = () => '/login'
describe('The login endpoint', () => {
it('returns 200', () => {
})
...
})
// Good. This assigment is loaded once when the file is required but it's evaluated before each test
beforeEach( () => {
letBe.loginUrl = () => '/login'
})
describe('The login endpoint', () => {
it('returns 200', () => {
})
...
})
To use a variable defined with letBe reference it like any other variable
before( () => {
letBe.loginUrl = () => '/login'
})
describe('The login endpoint', () => {
it('returns 200', () => {
const response = httpClient.get(loginUrl)
expect(response.statusCode).to.eql(200)
})
})
It is possible to override letBe variables at any scope and still reference them from outer scopes
before( () => {
letBe.httpClient = () => new HttpClient()
letBe.userUrl = () => `/users/${userId}`
letBe.httpResponse = () => httpClient.get(userUrl)
letBe.responseStatusCode = () => httpResponse.statusCode
})
describe('The users endpoint', () => {
describe('for an existent user', () => {
beforeEach( () => {
letBe.userId = () => 1
})
it('returns 200', () => {
expect(responseStatusCode).to.eql(200)
})
})
describe('for an inexistent user', () => {
beforeEach( () => {
letBe.userId = () => 2
})
it('returns 404', () => {
expect(responseStatusCode).to.eql(404)
})
})
})
The differences between using a regular variable assigment
const loginUrl = '/login'
describe('The login endpoint', () => {
...
})
and a letBe definition are
const variablesIf there is common or complex code in the tests is can be extracted to a method with the statement
letBeMethod.sum = function(a, b) { return a + b }
letBeMethod statements behave exactly like letBe variables.
Express expectations on Promises with
const chai = require('chai')
const {expect} = require('chai')
const {LetBeExtension, AsyncExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(AsyncExtension)
describe('A test expecting a promised value', () => {
before(() => {
letBe.promisedValue = () => Promise.resolve(1)
})
it('validates the promise resolution', (done) => {
expect(promisedValue).to.eventually.be.above(0)
.endWith(done)
})
})
The expectation must end with .endWith(done)
These assertions are available in the CollectionExtension
const chai = require('chai')
const {expect} = require('chai')
const {LetBeExtension, CollectionExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(CollectionExtension)
describe('A test', () => {
it('expects a single item', () => {
expect(aCollection).onlyItem.to.be ...
})
it('expects at least 1 item', () => {
expect(aCollection).firstItem.to.be ...
})
it('expects at least a second item', () => {
expect(aCollection).secondItem.to.be ...
})
it('expects at least a third item', () => {
expect(aCollection).thirdItem.to.be ...
})
it('expects at least a n items', () => {
expect(aCollection).atIndex(n).to.be ...
})
it('expects at least 1 last item', () => {
expect(aCollection).lastItem.to.be ...
})
})
These expectations usually get along well with .satisfy, .suchThat and .expecting
it('expects a collection with exactly one item', () => {
expect(users).onlyItem.to.be.suchThat((user) => {
expect(user.name).to.equal('John')
expect(user.lastName).to.equal('Doe')
})
})
and .samePropertiesThan
it('expects a collection with exactly one item', () => {
expect(users).onlyItem.to.have.samePropertiesThan({
name: 'John',
lastName: 'Doe'
})
})
These assertions are available in the ChainExtension
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, ChainExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(ChainExtension)
To express custom expectations on a nested attribute do
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, ChainExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(ChainExtension)
it('expects a collection with exactly one item with a given name', () => {
expect(users).onlyItem.suchThat((user) => {
expect(user.name).to.equal('John')
})
})
To assert that an action has some side effect on other objects do
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, ChainExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(ChainExtension)
it('expects a block to have an effect', () => {
let value = 0
expect(()=>{
value += 1
}).to.make(()=> value).to.equal(1)
})
Note that make always takes a block as its parameter and not a value.
The reason is that a parameter would be evaluated before calling the .make assertion
and therefore before the evaluation of the expect block.
This expectation can be used to test state after some event like a click or receiving a request occurs.
Changes the subject of the assertion chain setting it to the result of the evaluation of the given block
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, ChainExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(ChainExtension)
it('expects a block to have an effect', () => {
expect(1).after((n)=>n*10).to.equal(10)
})
This expectation can be combined with .make to express assertions like
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, ChainExtension} = require('o-testing-toolbox')
it('expects a button to invoke its onClicked handler', () => {
const user = new UserEmulator()
let clickedValue = false
const button = new Button({ onClicked: ()=>{clickedValue = true} })
expect(button)
.after((btn)=> user.click(btn))
.to.make(()=>clickedValue).to.be.true
})
})
as a simple alternative to the use of mock assertions.
Takes a block with the subject as its parameter to allow multiple assertions on the subject.
For example
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, ChainExtension} = require('o-testing-toolbox')
it('expects a list to have two items', () => {
expect(list).expecting((list) => {
list.to.have.firstItem.equalTo(10)
list.to.have.secondItem.equalTo(11)
})
})
With a regular assertions chain it would not be possible to make further assertions on the list object after the .firstItem assertion since it would have change the assertion subject.
After the evaluation of the block it is possible to continue with assertions on the original subject.
For example
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, ChainExtension} = require('o-testing-toolbox')
it('expects a list to have two items', () => {
expect(list).expecting((list) => {
list.to.have.firstItem.equalTo(10)
list.to.have.secondItem.equalTo(11)
}).to.have.length(2)
})
equalTo, eqlTo and matching expectations are synonyms of equal, eql and match.
They are meant to be used when they might make an assertion more readable, for example
expect(component).to.have.div.with.text.equalTo('A text')
Assert expectations on nested structures with
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, JsonExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(JsonExtension)
describe('A test', () => {
beforeEach(() => {
letBe.expectedObject = {
order: {
id: 1,
products: [
oranges: 1
]
}
}
})
it('expects all the nested properties to match exactly', () => {
expect(response.body).to.have.samePropertiesThan(expectedObject)
})
})
If you need only a partial match do
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, JsonExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(JsonExtension)
describe('A test', () => {
beforeEach(() => {
letBe.expectedObject = {
order: {
products: [
oranges: 1
]
}
}
})
it('expects the actual object to have the expectedProperties but it is ok if it has others', () => {
expect(response.body).to.have.allPropertiesIn(expectedProperties)
})
})
If you need to assert that the actual object does not have other properties than the expected ones do
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, JsonExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(JsonExtension)
describe('A test', () => {
beforeEach(() => {
letBe.expectedObject = {
order: {
products: [
oranges: 1
]
}
}
})
it('expects the actual object not to have other properties that the ones in expectedProperties', () => {
expect(response.body).to.have.noOtherPropertiesThan(expectedProperties)
})
})
If you need to assert on some nested property for a custom expectation use a function instead of a value:
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, JsonExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(JsonExtension)
describe('A test', () => {
beforeEach(() => {
letBe.expectedObject = {
id: (value) => { expect(value).to.be.a('integer').above(0) } // <-- custom expectation
order: {
products: [
oranges: 1
]
}
}
})
it('expects the actual object not to have other properties that the ones in expectedProperties', () => {
expect(response.body).to.have.allPropertiesIn(expectedProperties)
})
})
This expectation block is useful to test for a text to match a regular expression and for a float to equal a constant
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, JsonExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(JsonExtension)
describe('A test', () => {
beforeEach(() => {
letBe.expectedObject = {
description: (value) => { expect(description).to.match(/Product:/) }
price: (value) => { expect(price).to.be.closeTo(1, 0.001) }
}
})
it('expects an object property with a float and a text to satisfy custom assertions', () => {
expect(response.body).to.have.allPropertiesIn(expectedProperties)
})
})
Assert expectations on a file with
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, FileExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(FileExtension)
describe('A test', () => {
it('expects a file to exist', () => {
expect('/path/to/someFile.txt').to.be.a.file
})
it('expects a file to have a content', () => {
expect('/path/to/someFile.txt').fileContents.to.match(/expression/)
})
it('expects a directory to exist', () => {
expect('/path/to/someDirectory').be.a.directory
})
})
The expected path
expect(path)
can be a String or any other object that implements a .toString() method.
Particularly it could be a Path object with a path.toString() method.
To create temporary files or directories during the tests use
const chai = require('chai')
const { expect } = require('chai')
const { TmpDir } = require('o-testing-toolbox')
beforeEach(()=>{
const tmpDir = new TmpDir()
const stylesDir = tmpDir.createDir('styles/')
const scriptFile = tmpDir.createFile({ path: 'scripts/main.js', contents: 'const a = 1' })
})
The files and directories created through TmpDir are unique for each test execution and reside in the /tmp directory of your operative system and will be eventually discarded.
For this extension to work first you need to install the following packages:
npm install --save-dev mocha
npm install --save-dev chai
Mocha reports tests that take longer to run than a configurable threshold.
In a regresion test you would like to run all tests but while you are developing a feature you may want to skip the slow ones.
To skip slow tests first run all the tests as usual to see which ones are reported to be slow
npm test
Once you have identified the slow ones on each slow test file include the SlowTestsExtension plugin for chai
const chai = require('chai')
const { expect } = require('chai')
const {SlowTestsExtension} = require('o-testing-toolbox')
chai.use(SlowTestsExtension)
and replace the original test
describe('...', () => {
it('this test is slow', () => {
// ...
})
})
with
describe('...', () => {
slow.it('this test is slow', () => {
// ...
})
})
It's also possible to flag an entire group as slow:
slow.describe('...', () => {
it('this test is slow', () => {
// ...
})
it('this one too', () => {
// ...
})
})
Finally if you want to run the tests skipping the ones flagged as slow do
skip_slow=true npm test
To run all the tests execute
npm test
as usual.
For this extension to work first you need to install the following packages:
npm install --save-dev mocha
npm install --save-dev chai
npm install --save-dev eslint
npm install --save-dev eslint-config-standard
npm install --save-dev eslint-plugin-import
npm install --save-dev eslint-plugin-node
npm install --save-dev eslint-plugin-promise"
npm install --save-dev eslint-plugin-standard"
To test that your source code complies with the coding standards and best practices create the test
const path = require('path')
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, SourceCodeExtension, SlowTestsExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(SourceCodeExtension)
chai.use(SlowTestsExtension)
describe('Coding style', () => {
before(() => {
letBe.eslintConfigFile = './utilities-config/.eslintrc.js'
})
slow.it('complies with standards', () => {
expect().to.complyWithCodingStyles({
eslintConfigFile: eslintConfigFile
})
})
})
Replace the variable letBe.eslintConfigFile with your own eslint config file or delete it if you don't use a custom config file.
For this extension to work first you need to install the following packages:
npm install --save-dev mocha@7
npm install --save-dev chai
npm install --save-dev nyc
Note that for this extension mocha version must be mocha@7 due to breaking changes in version mocha@8
To test that the tests coverage is above an expected minimum create a new test file with the following contents:
const chai = require('chai')
const { expect } = require('chai')
const {LetBeExtension, SourceCodeExtension, SlowTestsExtension} = require('o-testing-toolbox')
chai.use(LetBeExtension)
chai.use(SourceCodeExtension)
chai.use(SlowTestsExtension)
const linesCoverageTarget = 100
describe('Testing suite covers', () => {
before( () => {
letBe.coveredLinesPercentage = linesCoverageTarget
letBe.thisFilename = path.basename(__filename)
letBe.fileExclusionPattern = `'**/${thisFilename}',`
letBe.mochaConfigFile = './utilities-config/.mocharc.json'
letBe.nycConfigFile = './utilities-config/nyc.config.json'
})
slow.it(`${linesCoverageTarget}% of the lines of code`, () => {
expect().linesCoverage({
excluding: fileExclusionPattern,
mochaConfigFile: mochaConfigFile,
nycConfigFile: nycConfigFile
}).to.be.at.least(coveredLinesPercentage)
})
})
Notes:
letBe.mochaConfigFile and nycConfigFile with your own mocha and nyc config files or delete them if you don't use a custom config file.skip_slow=true npm test
FAQs
Additional expectations for `chai` package and other testing utilities
We found that o-testing-toolbox 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
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.