"Test environment application"
Tea is a software testing suite, similar to node-tap
, tape
, mocha
, jasmine
, etc, but much smaller, more lightweight, and with fewer features.
- fast, lightweight, minimal code (~4.3kb minified and gzipped)
- very little setup, a fairly complete solution
- includes assertions, test harness, test runner, test reporter
- all in a single file,
src/tea.js
- supports flexible syntax for writing your tests:
- multiple assertion methods and syntaxes
- grouped and nested tests
- report results in various formats (console, DevTools, TAP, debug)
- works everywhere:
- Browsers - show test results in the DevTools console
- NodeJS - show test results in the terminal
- CI environments - show results in the terminal
- supports the following CLI options:
--quiet
: only show failing tests--fail-fast
: exit after the first failing test--verbose
: show expected/actual for all tests (including passing tests)--format=console|tap|debug
: the format of the test results--no-indent
: don't indent grouped results (useful if passing test results to a TAP-format prettifier)
Install tea
as a development dependency:
npm install @scottjarvis/tea --save-dev
Create a folder for your test files:
mkdir ./tests
Create a file to contain your tests:
touch ./tests/mytests.js
Initialise tea
in tests/mytests.js
:
tea = require('tea');
tea() /* initialise tea by running it */
// add test stuff here
tea
has various built-in assertion methods, and you should use them. But tests can be as simple as an expression.
Lets add some tests, so you have this in tests/mytests.js
:
tea = require('tea');
tea() /* initialise tea by running it */
/* Optionally define some test hooks..
test.beforeEach = () => console.log("before each")
test.beforeAll = () => console.log("before all")
test.afterEach = () => console.log("after each")
test.afterAll = () => console.log("after all")
*/
// tests without assertions or expected parameter (not recommended for TAP output)
test("simplest test", 1 === 1)
test("simplest test fails", 1 === 2)
// tests without assertions, with an 'expected' parameter
test("simple test, 1 + 1 should be '2'", 1+1, 2)
test("simple test fails, 1 + 1 should be '2'", 1+1, 3)
// a test using 'expect' assertion method
test("test using 'expect', description here", () => {
expect("1 + 1 should equal 2 is true", 1 + 1 === 2, true)
expect("1 + 1 should equal 2", 1 + 1, 2)
expect("app should be undefined", typeof app, "undefined")
})
// run the tests
run()
Now you're ready to run your tests:
node tests/mytests.js
You should see (something like) this:
Usage: node path/to/tests.js [options]
Options:
--quiet Only list failing tests
--fail-fast Exit after first failing test
--verbose List the actual/expected results of passing tests too
--format=<name> The style/format of the test results (console|debug|tap)
--no-indent Don't indent grouped test results (useful if piping to TAP prettifiers)
--help Show this help screen
For continuous integration (CI) environments, you might want to enable --quiet --fail-fast
.
To show test results in TAP format, use --format=tap
on the command-line (or tea.reportFormat = 'tap'
if in the browser/DevTools).
The TAP format is machine-readable, and you can pipe the results to other programs, to prettify it.
The TAP output of tea
is already indented and colourised a bit, to make it easier to read.
But these TAP prettifiers work OK with the TAP output of tea
:
Example (using tap-nyan
)
NOTE: If piping to a TAP prettifier, it's recommended to use the --no-indent
(or tea.noIndent = true
) setting.
You can of course use assertions, instead of simple expressions.
The following assertions are built into tea
:
assert
expect
should
(alias of expect)t
Usage:
expect(msg, actual, expected)
Examples:
/* tests using the 'expect' assertion method */
test("test using expect, description here", () => {
expect("1 + 1 should equal 2 is true", app.add(1, 1) === 2, true)
expect("1 + 1 should equal 2", app.add(1, 1), 2)
expect("app.c should be undefined", typeof app.c, "undefined")
})
// run the tests
run()
Usage:
assert.foo(msg, actual, expected)
Where foo
can be any of these methods:
assert.truthy()
- checks if something is truthyassert.falsey()
- checks if something is falseyassert.eq()
- uses the==
comparison (alias ofassert.equals()
)assert.strictEquals()
- uses the===
comparisonassert.deepEquals()
- uses thereact-fast-compare
deep equals comparisonassert.isType()
- uses atypeof
comparisonassert.throwsError()
- checks if something throws an Errorassert.isImmutable()
- checks if something is a Boolean, Number, String, Nullassert.isMutable()
- checks if something is an Object, Array, Function, Class, Map, or Setassert.isReactElement()
- checks object contains$$typeof = REACT_ELEMENT_TYPE
Examples:
/* tests using the 'assert' assertion method */
test("test using assert, description here", () => {
assert.isType("app.add should be a function", app.add, "function")
assert.isType("[1,2,3] should be an array", [1,2,3], "array")
assert.equals("1 should equal 1", 1, 1)
assert.strictEquals("1 + 1 should equal 2", app.add(1, 1), 2)
assert.deepEquals(
"simple obj1 should equal simple obj1",
{ foo: "bar" },
{ foo: "bar" }
)
var obj1 = { name: "dan", age: 22, stats: { s: 10, b: 20, c: 31 } }
var obj2 = { name: "bob", age: 21, stats: { s: 10, b: 20, c: 30 } }
assert.deepEquals("obj1 should equal obj2", obj1, obj2)
assert.truthy("string '' is truthy", "")
assert.truthy("string 'foo' is truthy", "foo")
assert.falsey("0 is falsey", 0)
assert.falsey("1 is falsey", 1)
assert.falsey("null is falsey", null, true)
assert.isMutable("Object is mutable", 10)
assert.isImmutable("Number is immutable", { foo: 99 })
assert.throwsError("Throws an error", "" + new Error())
assert.throwsError("Throws an error", new Error())
})
// run the tests
run()
The assert
method can also passed an object.
Usage:
assert({
message: "some string",
actual: foo,
expected: bar,
})
Examples:
/* tests using 'assert' with object syntax */
test("test using assert, object syntax", () => {
assert({
message: "10 should equal 10",
expected: 10,
actual: app.add(5, 5)
})
assert({
message: "10 should equal 10 ...",
expected: true,
actual: app.add(5, 5) === 10
})
assert({
message: 'assertion should assume expected=true (missing "expected" field)',
actual: app.add(1, 1) === 2
})
})
// run the tests
run()
Usage:
t.foo(actual [, expected])
Where foo
can be any of these methods:
t.ok()
- checks if something is truthyt.notOk()
- checks if something is falseyt.equal()
- uses the==
comparisont.strictEqual()
- uses the===
comparisont.deepEqual()
- uses thereact-fast-compare
deep equals comparisont.throws()
- checks if something throws an errort.type()
- check if something is of a specific type
(Note the difference to tap
and tape
- there's no msg
as the third parameter)
/* using TAP style assertions */
test("test using t", () => {
t.equal(1, 1)
t.strictEqual(1, "1")
t.deepEqual([1, 2, 3], [1, 2, 3])
t.deepEqual([1, 2, 3], [1, 3, 4])
t.deepEqual({ one: 1, foo: "baz" }, { one: 1, foo: "bar" })
t.throws(new Error())
t.throws("I'm just a String")
t.type("I'm of type String", "function")
t.type("I'm of type String", "string")
})
Tests can be grouped arbitrarily, using the group
function. Example:
/* group tests using the 'group' function */
group("First group of tests", () => {
test("some message", () => {
assert.strictEquals("one should equal one", 1, 1)
})
test("some message", () => {
assert.strictEquals("one should equal one", 1, 1)
})
})
The group
function has these aliases: feature
, scenario
, describe
and given
.
The test
function also has these aliases: it
and when
.
This means you can group your tests however you like, and write them in a number of styles:
/* jasmine or mocha style tests */
describe("Scenario: maths", () => {
it("should add up the numbers:", () => {
expect("1 + 1 equals 2", app.add(1, 1), 2)
expect("1 + 1 equals 2 (fails)", app.add(1, 1), 3)
})
})
/* some (made-up) gherkin style tests */
given("Scenario 3: some maths", () => {
when("we add up the numbers:", () => {
should("equal 2", app.add(1, 1), 2)
should("equal 2 (fails)", app.add(1, 1), 3)
})
})
/* larger BDD/gherkin/cucumber style tests */
feature("Calculator", () => {
scenario("Addition and subtraction", () => {
test("Subtractions", () => {
expect("1 - 1 equals 0", demoApp.sub(1, 1) === 0, true)
expect("1 - 1 equals 1", demoApp.sub(1, 1), 1)
})
test("Additions", () => {
expect("1 + 1 equals 2", demoApp.add(1, 1), 2)
expect("1 + 1 equals 2", demoApp.add(1, 1), 3)
})
})
scenario("Some mathsy scenario", () => {
test("Dividing numbers", () => {
expect("10 / 2 equals 5", demoApp.div(10, 2), 5)
})
test("Working with nonsense", () => {
// isNaN reports its type as number, so use this:
assert.isType("1 + 10 is a number", 1 + 10, "number")
// 1 + "foo" returns string
assert.isType("1 + 'foo' is not a number", 1 + 'foo', "string")
// 1 - "foo" returns isNaN
assert.isType("1 - 'foo' is not a number", 1 - 'foo', "number", false)
})
})
})
The test results output will be indented appropriately, like so:
Like many TAP-style test suites, tea
can run without needing any globals or "magic" variables, that seem to come from nowhere.
To run tea
in a way that does not use globals, see examples/no-globals-tests.js
:
tea = require("../src/tea.js")
/*
* Follow the TAP style of NOT creating any globals (except 'tea' itself)
*/
// tea() /* Dont init tea, then it won't register any globals */
/* OPTIONAL - set the default args directly */
tea.reportFormat = "tap"
tea.noIndent = true
/* OPTIONAL - alternatively, parse command line arguments (if any) */
tea.getArgs()
// Begin tests, without using globals
// using TAP style assertions - but no 'msg' as 3rd param to t()
tea.test("test one, using tea.t", t => {
t.equal(1, 1)
t.strictEqual(1, "1")
t.deepEqual([1, 2, 3], [1, 2, 3])
})
tea.test("test two, using tea.t", t => {
t.deepEqual([1, 2, 3], [1, 2, 3])
t.deepEqual([1, 2, 3, 4], [4, 5])
t.deepEqual({ one: 1, foo: "baz" }, { one: 1, foo: "bar" })
t.throws(new Error())
t.throws("I'm just a String")
// using other assertions
tea.expect("Foo is bar", "foo", "bar")
})
tea.run()
In summary, to avoid using globals with tea
:
- don't call
tea()
before your tests - call
tea.test()
instead of justtest()
- pass in
t
as a param to your tests... - ...or use
tea.assert
,tea.expect
instead ofassert
,expect
- call
tea.run()
instead of justrun()
If setting up a headles browser is too much work, you can use tea
to test stuff in a real page, using a regular old <script>
tag:
<script src="https://unpkg.com/@scottjarvis/tea"></script>
<script>
tea() // init tea
test("check text in page", function(){
var el = document.getElementById('#myElem')
assert.strictEquals("elem contains correct text", el.innerText, "expected text")
})
run() // run tests
</script>
You can even copy and paste dist/tea.umd.js
into the DevTools console directly! ...Then just write/paste some tests in the console and call run()
.
NOTE:
In the browser, passing command-line options like --quiet
won't work, but you can set these options in the DevTools directly instead, and then call run()
:
tea.quiet = true|false
tea.args.verbose = true|false
tea.failFast = true|false
tea.reportFormat = 'console|tap|debug'
You'll be able to see the test results in the DevTools:
If using the DevTools to see your test results, you can set tea.reportFormat = 'debug'
, to see your test results in a clickable, filterable console.table()
:
NOTE: If running tests using Node, you can set --format=debug
to see the AssertionError stack traces ;)
You might want to test your app in a real browser, without having to use <script>
tags and the DevTools. Or, you might need to run tests in a real browser on a system that has no desktop - such as a continuous integration (CI) environment.
For these cases, you can use a 'headless browser'.
To do so, it's recommended to use GhostJS with tea
. Install it like so:
npm i ghostjs --save-dev
GhostJS is a wrapper around the following headless browsers: phantomjs
, chrome
, slimerJS
(FireFox).
It enables a nice syntax when dealing with headless browsers.
However, since ghostjs
uses async/await you'll also need use babel
to run your tests. At a minimum you should install these packages:
npm install babel-preset-es2015 --save-dev
npm install babel-preset-stage-0 --save-dev
In a file named .babelrc
(in the root/top-level folder of your project):
{
"presets": ["es2015", "stage-0"]
}
You can then use ghostjs
async/await stuff inside your tests:
tea = require('tea');
ghost = require('ghostjs');
tea() // init tea
test("check the page loaded OK", async () => {
// Get page title
await ghost.open('http://google.com')
let pageTitle = await ghost.pageTitle()
assert.strictEquals(pageTitle, 'Google')
// Get the content of the body
let body = await ghost.findElement('body')
assert.strictEquals(await body.isVisible(), true)
})
...
Run your GhostJS enabled tests like so:
ghostjs --browser phantom tests/*.js
NOTE: you can replace 'phantom' with 'chrome' or 'firefox'.
Look in src/tea.js
, make any changes you like.
Rebuild the bundles in dist/
using this command: npm run build
- Add
skip.test(...)
to easily skip tests - Add more assertions:
assert.matchesRegex
- ensurefoo
matches a regexassert.matchesRegex(msg, regex, expected)
- etc
- Document testing of the following:
- React components (see https://reactjs.org/docs/test-utils.html#shallow-rendering)
- Preact components (also see shallow rendering)
- Add simple, built-in integration testing support:
- include wrapper/support for PhantomJS (see
node-phantomjs-simple
) - bundle phantomjs (32bit and 64bit builds)
- include wrapper/support for PhantomJS (see
- Add better stack traces - resolve them and cut out the irrelevant stuff
- Some kind of snapshotting
Pull Requests welcome ;)
- microbundle - used to bundle
tea
into various builds (indist/
) - react-fast-compare - the deep equals function used in
tea
- fast-deep-equals - a faster alternative to the above
- hyperon - borrowed functions for checking for React components in
tea
- ghostjs - a nice API for handling headless browsers
- nightmareJS - a nice alternative to GhostJS that uses Electron as it's headless browser