Internationalisation
Bundled Plugins API
spec
An synchronous/asynchronous test library for Lua.
This library uses a syntax similar to Ruby RSpec or Mocha.js.

Simple Synchronous Test

To create a test, create a new file ending with _spec.lua. For example, simple_spec.lua:
1
local spec = require "cp.spec"
2
local it = spec.it
3
4
return it "always passes"
5
:doing(function()
6
assert(true, "This always passes")
7
end)
Copied!
It can be run from the Debug Console like so:
1
cp.spec "simple" ()
Copied!
It will report something like this:
1
2019-10-06 18:13:28: [RESULT] it always passes: passed: 1; failed: 0; aborted: 0; time: 0.0022s
Copied!

Simple Synchronous Failure

If a test fails, it gives a report of where it failed, and if provided, the related message:
1
local spec = require "cp.spec"
2
local it = spec.it
3
4
return it "always fails"
5
:doing(function()
6
assert(false, "This always fails")
7
end)
Copied!
This will result in something like this:
1
2019-10-06 21:54:16: [FAIL] it always fails: [.../simple_spec.lua:6] This always fails
2
2019-10-06 21:54:16:
3
2019-10-06 21:54:16: [RESULT] it always fails: passed: 0; failed: 1; aborted: 0; time: 0.0370s
Copied!
You can then check the line that failed and resolve the issue.

Simple Asynchronous Test

Performing an asynchronous test is only a little more complicated. We'll modify our simple_spec.lua to use of the Run.This instance available to every test:
1
local spec = require "cp.spec"
2
local it = spec.it
3
local timer = require "hs.timer"
4
5
return it "always passes"
6
:doing(function(this)
7
this:wait(5)
8
assert(true, "This happens immediately")
9
timer.doAfter(2, function()
10
assert(true, "This happens after 2 seconds.")
11
this:done()
12
end)
13
end)
Copied!
Other than using hs.timer to actually make this asynchronous, the key additions here are:
  • this:wait(5): Tells the test that it is asynchronous, and to wait 5 seconds before timing out.
  • this:done(): Called inside the asynchronous function to indicate that it's complete.
Asycnchronous (and synchronous) tests can also be terminated by a failed assert, an error or a call to this:fail(...) or this:abort(...)

Multiple tests

Most things you're testing will require more than a single test. For this, We use Specification, most simply via the describe function:
1
local spec = require "cp.spec"
2
local describe, it = spec.describe, spec.it
3
4
local function sum(a,b)
5
return a + b
6
end
7
8
return describe "sum" {
9
it "results in 3 when you add 1 and 2"
10
:doing(function()
11
assert(sum(1, 2) == 3)
12
end),
13
it "results in 0 when you add 1 and -1"
14
:doing(function()
15
assert(sum(1, -1) == 0)
16
end),
17
}
Copied!
This will now run two tests, and report something like this:
1
2019-10-06 21:40:00: [RESULT] sum: passed: 2; failed: 0; aborted: 0; time: 0.0027s
Copied!

Data-driven Testing

When testing a feature, there are often multiple variations you want to test, and repeating individual tests can get tedious.
This is a great place to use the where feature. Our previous test can become something like this:
1
return describe "sum" {
2
it "results in ${result} when you add ${a} and ${b}"
3
:doing(function(this)
4
assert(sum(this.a, this.b) == this.result)
5
end)
6
:where {
7
{ "a", "b", "result"},
8
{ 1, 2, 3 },
9
{ 1, -1, 0 },
10
},
11
}
Copied!
Other variations can be added easily by adding more rows.

Running Multiple Specs

As shown above, you can run a single spec like so:
1
cp.spec "path.to.spec" ()
Copied!
You can also run that spec an all other specs under the same path by adding ".*" to the end.
1
cp.spec "path.to.spec.*" ()
Copied!
Or run every spec in your system like so:
1
cp.spec "*" ()
Copied!

Submodules

API Overview

API Documentation

Functions

describe

Signature
cp.spec.describe(name) -> function(definitions) -> cp.spec.Specification
Type
Function
Description
Returns a function which will accept a list of test definitions,
Parameters
  • name - The name of the test suite.
Returns

find

Signature
cp.spec.find(idPattern) -> cp.spec.Definition
Type
Function
Description
Attempts to find specs that match the provided ID pattern.

is

Signature
cp.spec.Handled.is(other) -> boolean
Type
Function
Description
Checks if the other is an instance of the Handled class.

it

Signature
cp.spec.it(name[, ...]) -> cp.spec.Scenario
Type
Function
Description
Returns an Scenario with the specified name and optional doingFn function.
Parameters
  • name - The name of the scenario.
  • doingFn - (optional) The function to call when doing the operation. Will be passed the Run.This instance for the definition.
Notes
  • See doing for more details regarding the function.
Signature
cp.spec.setSearchPath(path)
Type
Function
Description
Sets the path that will be used to search for spec files with the spec "my.extension" call.
Parameters
  • path - The path to search for spec files. Set to nil to only search the default package path.

spec

Signature
cp.spec(id) -> cp.spec.Definition
Type
Function
Description
This will search the package path (and specPath, if set) for _spec.lua files.
Parameters
  • id - the path ID for the spec. Eg. "cp.app"
Returns

test

Signature
cp.spec.test(id) -> cp.spec.Definition
Type
Function
Description
Attempts to load a cp.test with the specified ID, converting
Parameters
  • id - The cp.test ID (eg. "cp.app").
Returns
  • The Definition or throws an error if it can't be found.
Last modified 1mo ago