It is rather easy to write plugins in order to extend the capabilities of expect
. Anyway, expect
will
prevent from adding the same feature several times, so plugin writers must carefully choose the name of the
features. Remember that features are case-insensitive, so you cannot add an equal
feature, neither can you
add an Equal
one.
The core.lua file itself is a plugin containing the core features. You can use it as an example to see how a plugin should be written.
Your plugin should return a function taking expect
as a single parameter. The function will add the
features (assertions) to expect
. You can add diffent kinds of features:
- properties, using
expect.addProperty(name[, fun])
, will add a feature callable as a property; you may do assertions in it, but remember that it is an error to terminate a statement with a property in LUA, so it is better to either do nothing (chainable word) or set a control data property; - methods, using
expect.addMethod(name[, fun])
, will add a feature callable as a method, which may take some parameters; - mixed, using
expect.addChainableMethod(name[, fun[, fun]])
, will add a feature callable both as a method (first provided callback) and as a property (second callback); both calls can have an action, but beware that the action executed for the property usage is also executed when the feature is called as a method.
If you don’t provide callbacks when using these functions, a no-op one will be used.
local FailureMessage = require('expect.FailureMessage')
return function(expect)
-- Add a no-op property (chaining word)
expect.addProperty('whatever')
-- Add a property setting a flag on control data
expect.addProperty('fluffy', function(controlData)
controlData.fluffy = true
end)
-- Add a method with a size parameter
expect.addMethod('longerThan', function(controlData, size)
controlData:checkType("string") -- Only applies to strings
if controlData.fluffy then -- See if this flag was previously set
size = size * 2
end
local params = { -- Prepare parameters for failure messages
size = size
}
controlData:assert(controlData.actual:len() > size, FailureMessage('expected {#} to be longer than {size}', params),
FailureMessage('expected {#} to not be longer than {size}', params))
end)
-- Add a mixed feature
expect.addChainableMethod('theAnswerToLifeUniverseAndEverything', function(controlData)
controlData:assert(controlData.actual == 42, FailureMessage('expected {#} to be 42'),
FailureMessage('expected {#} to not be 42'))
end, function(controlData)
controlData.answer = 42
end)
end
All this may be used this way:
expect("a long string").to.be.fluffy.And.theAnswerToLifeUniverseAndEverything.but.longerThan(3)
expect(42).to.whatever.be.theAnswerToLifeUniverseAndEverything()
The first parameter provided to a feature function is a ControlData
object. This object can contain any
data needed for the assertion. The object is shallow-copied between each chained feature. If you add your own
property to this object, be careful to choose the name in order to prevent conflicts with other plugins.
To create a ControlData
object, simply call ControlData(data)
where data
is either a table or a
ControlData
object which will be shallow-copied. But you probably do not need to directly create a
ControlData
object, this kind of object is usually created through the Expect
object.
Not counting plugin additions, the ControlData
object contains the following properties and functions:
This is the actual object being tested. Usually, this is the one provided to the expect
function as first
parameter, but this can be modified by a feature.
This property is set to true if the user called the not
feature earlier in the assertion. It can be either
nil
or false
otherwise.
You may call this function to ensure that actual object is of appropriate type. If checkNegation
is true
,
then the check will be inverted if the assertion is negated. The function does not return any value but fails
if the type is not of expected type.
This function can be used to ensure that the actual object is callable, i.e. either a function or a table
with a metatable defining a __call
function. If checkNegation
is true
, then the check will be inverted
if the assertion is negated. The function does not return any value but fails if the actual object is not
callable.
Call this function to process your assertion. If a negativeMsg
is provided (not nil
), then the function
will check if the assertion is negated and invert its behavior accordingly. Otherwise, the function fails if
expr
is false.
positiveMsg
is the message to display (FailureMessage
) ifexpr
is false and the assertion is not inverted;negativeMsg
is the message to display (FailureMessage
) ifexpr
is true and the assertion is inverted;level
is a the level of functions to be ignored when throwing the error in order to show the real source of the error; you usually should not need to set it.
This function make the assertion fail immediately, displaying the provided message
(FailureMessage
). The
level
parameter is the level of functions to be ignored when throwing the error in order to show the real
source of the error; you usually should not need to set it.
The Expect
object is the one created by the expect()
function and all consecutive features called. This
object does not contain any accessible data by itself, but it creates the ControlData
object used when
calling the features, and it redirects every property request to the appropriate feature.
An Expect
object can be created by calling Expect(data)
, where data
is given to ControlData
constructor.
A FailureMessage
object is used to provide a message composed of a pattern and parameters. The message will
only be constructed before being used, which prevent loosing time creating an unneeded string. Parameters may
be formatted, depending on the pattern, in order to be clearly readable by the end-user.
To create a FailureMessage
object, call FailureMessage(pattern, parameters)
where pattern
is the
pattern and parameters
is a table containing the parameters: keys are the parameter names and values are
the values to show to end user. If a parameter is referenced in the pattern but is not in the parameters
table it will not be considered empty but nil
(displayed as (nil)
).
The pattern is a string containing placeholders for the parameters in the format {name}
where name
is the
name of the parameter. This name should only be made of letters, bad things may happen otherwise.
You do not need to provide actual
to the parameters, it will be automatically added and can be displayed in
the message using the placeholder {#}
.
If you want to add content without formatting, you can add use the format {!name}
for the placeholder. The
parameter name
will then be displayed using a simple tostring
and no complex formatting.
Your pattern must not contain opening curly brace {
except to indicate the start of a placeholder. If you
need to display an opening curly brace, add an empty placeholder {}
.
This function is used internally to set the value of actual
. You usually do not need to call it yourself.
This function is used internally to create the displayed message from the pattern and the parameters. This
function is called when calling tostring(msg)
with a FailureMessage
object.
Objects of type DiffTable
are containing an initial
table and a diffs
array to identify differences
with another table. To create a DiffTable
, simply call DiffTable(initial, diffs)
, but you usually should
not need to create such object as it is created automatically when comparing tables.
A DiffTable
object can be directly provided as a FailureMessage
parameter and will be displayed with the
differences highlighted.
This function can be called on any object in order to check if this object is a DiffTable
object. The
result is true
if it is the case, false
otherwise.
This function compares the two provided items. If they are tables, they will be deep compared. The result of
the comparison is a boolean (true
if objects are same) and either a copy of each object or a DiffTable
instead of each object if it makes sense.