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

  • Functions - API calls offered directly by the extension
  • ​describe​
  • ​find​
  • ​is​
  • ​it​
  • ​setSearchPath​
  • ​spec​
  • ​test​

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.

​setSearchPath​

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.