Skip to content

Commit

Permalink
feat: single test case object (#54)
Browse files Browse the repository at this point in the history
* add failing test for the test object

* add test case object support

* update docs

* more linting
  • Loading branch information
bahmutov authored Nov 11, 2022
1 parent 74334cb commit c96bf31
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 23 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,22 @@ import { testTitle, testDataItem } from './utils'
it.each(data, 3, 2)(testTitle, testDataItem)
```

## Test case object

Sometimes you just want to have a single object that has all the tests cases together with the inputs. You can pass an object instead of an array to the `it.each` function. Each object key will become the test title, and the value will be passed to the test callback. If the value is an array, it will be destructured. See [object-input.cy.ts](./cypress/e2e/object-input.cy.ts) spec file for details.

```ts
const testCases = {
// key: the test label
// value: list of inputs for each test case
'positive numbers': [1, 6, 7], // [a, b, expected result]
'negative numbers': [1, -6, -5],
}
it.each(testCases)((a, b, expectedResult) => {
expect(add(a, b)).to.equal(expectedResult)
})
```

## Specs

Find the implementation in [src/index.js](./src/index.js)
Expand Down
43 changes: 43 additions & 0 deletions cypress/e2e/object-input.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import '../../src'

function add(a: number, b: number) {
return a + b
}

const testCases = {
// key: the test label
// value: list of inputs for each test case
'positive numbers': [1, 6, 7], // [a, b, expected result]
'negative numbers': [1, -6, -5],
}

describe('converted to an array of arrays', () => {
// we can convert the above object ourselves
const pairs = Cypress._.toPairs(testCases)
it.each(pairs)('testing %0', (title, numbers) => {
const [a, b, expectedResult] = numbers
expect(add(a, b)).to.equal(expectedResult)
})
})

// https://github.com/bahmutov/cypress-each/issues/53
describe('automatic conversion', () => {
it.each(testCases)((a: number, b: number, expectedResult: number) => {
expect(add(a, b)).to.equal(expectedResult)
})

context('plain values', () => {
const evenCases = {
two: 2,
four: 4,
}

function isEven(x: number) {
return x % 2 === 0
}

it.each(evenCases)((x) => {
expect(x).to.satisfy(isEven)
})
})
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"src"
],
"scripts": {
"lint": "tsc --pretty --allowJs --strict --noEmit src/index.js cypress/**/*.js",
"lint": "tsc --pretty --allowJs --strict --noEmit src/index.js cypress/**/*.js cypress/**/*.ts",
"test": "cypress-expect run --expect cypress/expected.json",
"semantic-release": "semantic-release"
},
Expand Down
21 changes: 21 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ type ItemPredicateFunction<T> = (item: T, index: number, items: T[]) => boolean

declare namespace Mocha {
type TestCallback<T> = (this: Context, arg0: T, arg1: any, arg2: any) => void
type TestCallbackAny = (
this: Context,
arg0: any,
arg1: any,
arg2: any,
) => void

interface TestFunction {
/**
Expand All @@ -23,6 +29,21 @@ declare namespace Mocha {
totalChunks?: number | ItemPredicateFunction<T>,
chunkIndex?: number,
): (titlePattern: string | TestTitleFn<T>, fn: TestCallback<T>) => void

/**
* A single test case object where the keys are test titles,
* and the values are used as inputs to the test callback
* @see https://github.com/bahmutov/cypress-each#test-case-object
* @example
* const testCases = {
* // key: the test label
* // value: list of inputs for each test case
* 'positive numbers': [1, 6, 7], // [a, b, expected result]
* 'negative numbers': [1, -6, -5],
* }
* it.each(testCases)((a, b, result) => { ... })
*/
each(testCases: object): (fn: TestCallbackAny) => void
}

interface SuiteFunction {
Expand Down
73 changes: 51 additions & 22 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ if (!it.each) {
values = Cypress._.range(0, values)
}

if (!Array.isArray(values)) {
throw new Error('cypress-each: values must be an array')
if (typeof totalChunks === 'number') {
if (!Array.isArray(values)) {
throw new Error('cypress-each: values must be an array')
}
}

return function (titlePattern, testCallback) {
Expand All @@ -83,30 +85,57 @@ if (!it.each) {
values = values.filter(totalChunks)
}

values.forEach(function (value, k) {
const title = makeTitle(titlePattern, value, k, values)
if (!title) {
if (Cypress._.isPlainObject(values)) {
testCallback = titlePattern
if (typeof testCallback !== 'function') {
throw new Error(
`Could not compute the test title ${k} for value ${value}`,
'When using a single test case object, cannot provide title pattern',
)
}

// define a test for each value
if (Array.isArray(value)) {
// const title = formatTitle(testTitle, ...value)
it(title, function itArrayCallback() {
return testCallback.apply(this, value)
})
} else {
// const title = formatTitle(testTitle, value)
it(title, function itCallback() {
return testCallback.call(this, value)
})
}
}, this)

// returns the number of created tests
return values.length
const pairs = Cypress._.toPairs(values)
pairs.forEach(function (pair) {
const [title, value] = pair
// define a test for each value
if (Array.isArray(value)) {
it(title, function itArrayCallback() {
return testCallback.apply(this, value)
})
} else {
it(title, function itCallback() {
return testCallback.call(this, value)
})
}
}, this)
} else if (Array.isArray(values)) {
values.forEach(function (value, k) {
const title = makeTitle(titlePattern, value, k, values)
if (!title) {
throw new Error(
`Could not compute the test title ${k} for value ${value}`,
)
}

// define a test for each value
if (Array.isArray(value)) {
it(title, function itArrayCallback() {
return testCallback.apply(this, value)
})
} else {
it(title, function itCallback() {
return testCallback.call(this, value)
})
}
}, this)

// returns the number of created tests
return values.length
} else {
console.error(values)
throw new Error(
'Do not know how to create tests from the values array / object. See DevTools console',
)
}
}
}
}
Expand Down

0 comments on commit c96bf31

Please sign in to comment.