Skip to content
This repository has been archived by the owner on Jun 9, 2023. It is now read-only.

Latest commit

 

History

History
239 lines (185 loc) · 10.2 KB

cp.spec.md

File metadata and controls

239 lines (185 loc) · 10.2 KB

docs » cp.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:

local spec          = require "cp.spec"
local it            = spec.it

return it "always passes"
:doing(function()
    assert(true, "This always passes")
end)

It can be run from the Debug Console like so:

cp.spec "simple" ()

It will report something like this:

2019-10-06 18:13:28: [RESULT] it always passes: passed: 1; failed: 0; aborted: 0; time: 0.0022s

Simple Synchronous Failure

If a test fails, it gives a report of where it failed, and if provided, the related message:

local spec          = require "cp.spec"
local it            = spec.it

return it "always fails"
:doing(function()
    assert(false, "This always fails")
end)

This will result in something like this:

2019-10-06 21:54:16:   [FAIL] it always fails: [.../simple_spec.lua:6] This always fails
2019-10-06 21:54:16:
2019-10-06 21:54:16: [RESULT] it always fails: passed: 0; failed: 1; aborted: 0; time: 0.0370s

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:

local spec          = require "cp.spec"
local it            = spec.it
local timer         = require "hs.timer"

return it "always passes"
:doing(function(this)
    this:wait(5)
    assert(true, "This happens immediately")
    timer.doAfter(2, function()
        assert(true, "This happens after 2 seconds.")
        this:done()
    end)
end)

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:

local spec          = require "cp.spec"
local describe, it  = spec.describe, spec.it

local function sum(a,b)
    return a + b
end

return describe "sum" {
    it "results in 3 when you add 1 and 2"
    :doing(function()
        assert(sum(1, 2) == 3)
    end),
    it "results in 0 when you add 1 and -1"
    :doing(function()
        assert(sum(1, -1) == 0)
    end),
}

This will now run two tests, and report something like this:

2019-10-06 21:40:00: [RESULT] sum: passed: 2; failed: 0; aborted: 0; time: 0.0027s

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:

return describe "sum" {
    it "results in ${result} when you add ${a} and ${b}"
    :doing(function(this)
        assert(sum(this.a, this.b) == this.result)
    end)
    :where {
        { "a",  "b",    "result"},
        { 1,    2,      3 },
        { 1,    -1,     0 },
    },
}

Other variations can be added easily by adding more rows.

Running Multiple Specs

As shown above, you can run a single spec like so:

cp.spec "path.to.spec" ()

You can also run that spec an all other specs under the same path by adding ".*" to the end.

cp.spec "path.to.spec.*" ()

Or run every spec in your system like so:

cp.spec "*" ()

Submodules

API Overview

API Documentation

Functions

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
Signature cp.spec.find(idPattern) -> cp.spec.Definition
Type Function
Description Attempts to find specs that match the provided ID pattern.
Signature cp.spec.Handled.is(other) -> boolean
Type Function
Description Checks if the other is an instance of the Handled class.
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.
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
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.