Introduction
Table Driven Tests is primarily the recommended testing method in Go lang. Define a complete test case as a table, including the input and expected results, and test iteratively against the test object. We can write the test suite only once and pass in the test data. If you have a lot of copy and paste when creating tests, it's likely that you can refactor your test cases into a table.
By the way, Go lang's Official states:
Writing good tests is not trivial, but in many situations a lot of ground can be covered with table-driven tests
jest also supports Table Driven Test, so I'd like to share how.
How to write a test case
You can express test cases in two ways with jest. Consider the following case where there is a conditional branch as a test target.
Function to convert *.html other than index.html to */index.html
import { dirname, join, parse } from 'path'export const path2IndexHtml = (path: string): string => {const EXT = '.html'const INDEX = 'index'const { ext, name, dir } = parse(path)if(ext !== EXT) return pathif(name === INDEX) return pathreturn join(dir, name, `${INDEX}${EXT}`)}
This function itself was used when the Server Side Generation implementation generating a file. Is it a rare case🙃
Test with an array of table
The first way to write it is to define the table as an array and pass it. Write as follows.
describe.each(table)(name, fn, timeout)it.each(table)(name, fn, timeout)test.each(table)(name, fn, timeout)
Some objects have a same
each method.
The specific test case is as follows.
import { path2IndexHtml } from '../src'describe('path2IndexHtml', () => {const table = [['', ''],['index.html', 'index.html'],['/index.html', '/index.html'],['index.css', 'index.css'],['about.css', 'about.css'],['about/index.css', 'about/index.css'],['about.html', 'about/index.html'],['hoge/about.html', 'hoge/about/index.html'],['/hoge/about.html', '/hoge/about/index.html'],['aindex.html', 'aindex/index.html'],['indexa.html', 'indexa/index.html'],['/about/index.html', '/about/index.html'],]it.each(table)('pattern1: path2IndexHtml(%s) = %s', (path, expected, fa) => {expect(path2IndexHtml(path)).toBe(expected)})})
Describe the test case in a two-dimensional array. The order of the elements in the array is passed as an argument to
fn.
Also, for
name, specify the title of the test suite.
You can generate a unique test title by injecting parameters that follow the format of
printf.
Please check here for details.
The parameters are passed in the order of the elements in the array.
By the way, if you pass a one-dimensional array, it will be converted internally as
[1, 2, 3] ->
[[1], [2], [3]].
Also, the parameters passed to
fn are type inferred for TypeScript.
In the above example,
table is of type
string[][], so the argument to
fn is inferred to be
... args: string [].
If you want to infer as a tuple, add
as const to
table and it will be inferred well.
The
each method also accepts generic types, so you can specify the type as follows:
it.each<string[]>(table)
When run the test, got the following output:
path2IndexHtml✓ path2IndexHtml() ->✓ path2IndexHtml(index.html) -> index.html✓ path2IndexHtml(/index.html) -> /index.html✓ path2IndexHtml(index.css) -> index.css✓ path2IndexHtml(about.css) -> about.css✓ path2IndexHtml(about/index.css) -> about/index.css (1 ms)✓ path2IndexHtml(about.html) -> about/index.html✓ path2IndexHtml(hoge/about.html) -> hoge/about/index.html✓ path2IndexHtml(/hoge/about.html) -> /hoge/about/index.html✓ path2IndexHtml(aindex.html) -> aindex/index.html✓ path2IndexHtml(indexa.html) -> indexa/index.html✓ path2IndexHtml(/about/index.html) -> /about/index.html
The parameters are embedded in the test title. I was able to test various parameters with a minimal test suite.
Test with tagged template literals
You can also represent a table with a tagged template literal.
The interface looks like this:
describe.each`table`(name, fn, timeout)it.each`table`(name, fn, timeout)test.each`table`(name, fn, timeout)
Write the same test as the above example, it will look like this:
describe('path2IndexHtml', () => {it.each`path | expected${''} | ${''}${'index.html'} | ${'index.html'}${'/index.html'} | ${'/index.html'}${'about.css'} | ${'about.css'}${'about/index.css'} | ${'about/index.css'}${'about.html'} | ${'about/index.html'}${'hoge/about.html'} | ${'hoge/about/index.html'}`('path2IndexHtml($path) -> $expected', ({ path, expected }) => {expect(path2IndexHtml(path)).toBe(expected)})})
The first line of
table specifies the variable name. Subsequent lines describe the test case with the
$ {value} syntax.
Even the
string type must be enclosed in
${}.
Since it is passed in the form of an object to the argument of
fn, it is better to receive it by destructuring assignment.
If you use a parameter for the test title of
name, you can access the variable in the form
$name.
The advantage of this notation is that you can write test cases in the form of a table.
However, for test cases with a lot of
string,
${} and quotes are not very easy to see.
Also, in this notation, the type inference of the argument of
fn becomes
any type.
This is unavoidable because it is a tagged template literal and cannot accept generics.
If you really want to type, define the type in the
fn function.
'path2IndexHtml($path) -> $expected',({ path, expected }: { path: string; expected: string }) => {expect(path2IndexHtml(path)).toBe(expected)}
The test results will be the same for both, so it's a good idea to use different notations depending on the test case and preference.
Edit this page on GitHub