- Notifications
You must be signed in to change notification settings - Fork2
🤖 Repeat tests. Repeat tests. Repeat tests.
License
ehmicky/test-each
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
🤖 Repeat tests. Repeat tests. Repeat tests.
Repeats tests using different inputs(Data-Driven Testing):
- test runner independent: works with your current setup
- generatestest titles that are descriptive, unique, for anyJavaScript type (not just JSON)
- loops over every possible combination of inputs(cartesian product)
- can use random functions (fuzz testing)
- snapshot testing friendly
Pleasereach outif you're looking for a Node.js API or CLI engineer (11 years of experience).Most recently I have beenNetlify Build'sandNetlify Plugins'technical lead for 2.5 years. I am available for full-time remote positions.
// The examples use Ava but any test runner works (Jest, Mocha, Jasmine, etc.)importtestfrom'ava'importmultiplyfrom'./multiply.js'import{each}from'test-each'// The code we are testing// Repeat test using different inputs and expected outputseach([{first:2,second:2,output:4},{first:3,second:3,output:9},],({ title},{ first, second, output})=>{// Test titles will be:// should multiply | {"first": 2, "second": 2, "output": 4}// should multiply | {"first": 3, "second": 3, "output": 9}test(`should multiply |${title}`,(t)=>{t.is(multiply(first,second),output)})},)// Snapshot testing. The `output` is automatically set on the first run,// then re-used in the next runs.each([{first:2,second:2},{first:3,second:3},],({ title},{ first, second})=>{test(`should multiply outputs |${title}`,(t)=>{t.snapshot(multiply(first,second))})},)// Cartesian product.// Run this test 4 times using every possible combination of inputseach([0.5,10],[2.5,5],({ title},first,second)=>{test(`should mix integers and floats |${title}`,(t)=>{t.is(typeofmultiply(first,second),'number')})})// Fuzz testing. Run this test 1000 times using different numbers.each(1000,Math.random,({ title},index,randomNumber)=>{test(`should correctly multiply floats |${title}`,(t)=>{t.is(multiply(randomNumber,1),randomNumber)})})
npm install -D test-each
This package works in both Node.js >=18.18.0 andbrowsers.
This is an ES module. It must be loaded usinganimport
orimport()
statement,notrequire()
. If TypeScript is used, it must be configured tooutput ES modules,not CommonJS.
import{each}from'test-each'constinputs=[['red','blue'],[0,5,50],]each(...inputs,(info,color,number)=>{})
Firescallback
once for each possible combination ofinputs
.
Eachinput
can be anarray
, afunction
or aninteger
.
A common use case forcallback
is to define tests (using any test runner).
info
is anobject
whose properties can be used to generatetest titles.
Each combination of parameters is stringified as atitle
available in thecallback
'sfirst argument.
Titles should be included in test titles to make them descriptive and unique.
Long titles are truncated. An incrementing counter is appended to duplicates.
Any JavaScript type isstringified,not just JSON.
You can customize titles either by:
- defining
title
properties ininputs
that areplain objects - using the
info
argument
import{each}from'test-each'each([{color:'red'},{color:'blue'}],({ title},param)=>{// Test titles will be:// should test color | {"color": "red"}// should test color | {"color": "blue"}test(`should test color |${title}`,()=>{})})// Plain objects can override this using a `title` propertyeach([{color:'red',title:'Red'},{color:'blue',title:'Blue'},],({ title},param)=>{// Test titles will be:// should test color | Red// should test color | Bluetest(`should test color |${title}`,()=>{})},)// The `info` argument can be used for dynamic titleseach([{color:'red'},{color:'blue'}],(info,param)=>{// Test titles will be:// should test color | 0 red// should test color | 1 bluetest(`should test color |${info.index}${param.color}`,()=>{})})
If severalinputs
are specified, theircartesian product is used.
import{each}from'test-each'// Run callback five times: a -> b -> c -> d -> eeach(['a','b','c','d','e'],(info,param)=>{})// Run callback six times: a c -> a d -> a e -> b c -> b d -> b eeach(['a','b'],['c','d','e'],(info,param,otherParam)=>{})// Nested arrays are not iterated.// Run callback only twice: ['a', 'b'] -> ['c', 'd', 'e']each([['a','b'],['c','d','e'],],(info,param)=>{},)
If afunction
is used instead of an array, each iteration fires it and usesits return value instead. Thefunction
is called with thesame argumentsas thecallback
.
The generated values are included intest titles.
import{each}from'test-each'// Run callback with a different random number each timeeach(['red','green','blue'],Math.random,(info,color,randomNumber)=>{})// Input functions are called with the same arguments as the callbackeach(['02','15','30'],['January','February','March'],['1980','1981'],(info,day,month,year)=>`${day}/${month}/${year}`,(info,day,month,year,date)=>{},)
Integers can be used instead of arrays to multiply the number of iterations.
This enablesfuzz testing when combinedwithinput functions and libraries likefaker.js,chance.js orjson-schema-faker.
importfakerfrom'faker'// Run callback 1000 times with a random UUID and color each timeeach(1000,faker.random.uuid,faker.random.arrayElement(['green','red','blue']),(info,randomUuid,randomColor)=>{},)// `info.index` can be used as a seed for reproducible randomness.// The following series of 1000 UUIDs will remain the same across executions.each(1000,({ index})=>faker.seed(index)&&faker.random.uuid(),(info,randomUuid)=>{},)
This library works well withsnapshot testing.
Any library can be used(snap-shot-it
,Ava snapshots,Jest snapshots,Node TAP snapshots, etc.).
import{each}from'test-each'// The `output` is automatically set on the first run,// then re-used in the next runs.each([{first:2,second:2},{first:3,second:3},],({ title},{ first, second})=>{test(`should multiply outputs |${title}`,(t)=>{t.snapshot(multiply(first,second))})},)
Ifcallback
'sparameters are directly modified, they should becopied to prevent side effects for the next iterations.
import{each}from'test-each'each(['green','red','blue'],[{active:true},{active:false}],(info,color,param)=>{// This should not be done, as the objects are re-used in several iterationsparam.active=false// But this is safe since it's a copyconstnewParam={ ...param}newParam.active=false},)
iterable()
can be used to iterate over each combinationinstead of providing a callback.
import{iterable}from'test-each'constcombinations=iterable(['green','red','blue'],[{active:true},{active:false}],)for(const[{ title},color,param]ofcombinations){test(`should test color |${title}`,()=>{})}
The return value is anIterable
.This can be converted to an array with the spread operator.
constarray=[...combinations]array.forEach(([{ title},color,param])=>{test(`should test color |${title}`,()=>{})})
inputs
:Array | function | integer
(one orseveral)callback
:(info, ...params) => void
Firescallback
with each combination ofparams
.
inputs
:Array | function | integer
(one orseveral)
Return value:Iterable<[info, ...params]>
Returns anIterable
looping through each combination ofparams
.
Type:object
Type:string
Likeparams
but stringified. Should be used intest titles.
Type:string[]
Likeinfo.title
but for eachparam
.
Type:integer
Incremented on each iteration. Starts at0
.
Type:integer[]
Index of eachparams
inside each initialinput
.
Type:any
(one orseveral)
Combination of inputs for the current iteration.
For any question,don't hesitate tosubmit an issue on GitHub.
Everyone is welcome regardless of personal background. We enforce aCode of conduct in order to promote a positive andinclusive environment.
This project was made with ❤️. The simplest way to give back is by starring andsharing it online.
If the documentation is unclear or has a typo, please click on the page'sEdit
button (pencil icon) and suggest a correction.
If you would like to help us fix a bug or add a new feature, please check ourguidelines. Pull requests are welcome!
About
🤖 Repeat tests. Repeat tests. Repeat tests.