Expect

This is a test-lib based on expectations, that is we say what the subject is expected to do. This is slightly different from the typical assertion style testing libs. Expectations are common in several testing frameworks, but the style implemented has its own quirks to make it work in our environment.

The main difference is that an assertion says what is wrong and stops executing, but an expectation says what is right and continue execution. In addition there are some magic behind the scenes to allow the subjects and expectations to be set up for repeated testing.

It will also create way better error messages than the usual assert-style testing, and in this respect it is more like Hamcrest frameworks.

Core idea

There are three access functions; subject(), expect(), and reports(). They all act as accessors to several internal structures, but in particular the first two acts as accessors to a Adapt.lua object. The first two also has their own stacks for storing objects assigned to them. That makes it possible to keep subjects and expectations for later testing, and especially to set up partial fixtures for repeated testing.

The call expect() is implemented as a variation of Adapt, where the variation allows calling this with any argument. This variation defines a test value given as an explicit argument, and run conditions against this value. We may say this is the closest we have to a plain assertion.

This call has a twin subject() which is nearly identical, but it is automatically set within calls to describe(), context(), and it() to be the same as the subject argument. As the state of the subject argument changes, so will the outcome of the subject() change.

There are several ways to implement objects in Lua, but we can safely assume that most objects we want to test will be based on tables. By implementing additional accessors we can test other types of objects too, but most of the time we will be testing tables.

Assume we have a rather simple library foo on the form

local h = {
  bar = function() return 'baz' end
}
return h

This is a typical form for most libraries, and we can include it in other libraries as

local foo = require 'foo'

and this can be used in our test fixtures as

subject = require 'foo'

The form local subject = require 'foo' should not be used inside our tests, as this will not work as expected. This creates a local variable, while we want to assign the required lib to a pre-existing variable. Or really we want to assign the required library to a pre-existing table by use of overloaded operators.

It is also possible to set the testee as a temporal, by instead use a call syntax, like subject( require 'foo' ). This will place the object in a temporary store in the condition object created by the access method.

Note: Testing are done on the object returned from the library, and if that object maintain a state, then it will be retained from call to call. We may say that we are testing a specific instance of the object and not the library. This can lead to bogus tests, so be careful!

In its simple form it is already ready for testing. The instance form submerges in the subject = require 'foo' line, and emerges in the conditions methods for expect. Because that is available through subject and expect we can add simple tests like

subject = require 'foo'
subject
  :bar() -- the 'bar' call is redirected
  :toBe( 'baz' ) -- gives true

or, alternatively

subject = require 'foo'
expect
  :bar() -- the 'bar' call is redirected
  :toBe( 'baz' ) -- gives true

or, in call-style

 subject(require 'foo')
   :bar() -- the 'bar' call is redirected
   :toBe( 'baz' ) -- gives true

The final toBe( 'baz' ) does a little magic, it uses the opposite value of the initial access method. If the initial access method is subject then it sets the temporal expect, if it is expect then it sets the temporal subject. It will not push the value on the subject or expect stacks.

Note in particular that the following gives the same result (here the testee 'a' is not a required library and is defined inline)

subject = 'a'
expect :toBe( 'a' ) -- gives true
expect( 'a' ) :toBeSame() -- gives true

this is also the same

subject = 'a'
expect = 'a'
subject :toBeSame() -- gives true
expect :toBeSame() -- gives true

The results from the tests are accessible through reports. In a Mediawiki-context the reports can be shown by calling mw.log

expect( 'baz' ) :toBe( 'baz' ) -- gives true
mw.log( report() ) -- gives a complete report

Library

Picks and transforms are pre-processes to run before the condition (tests for similarity), which is our idea of what is a correct answer. After the tests there are additional post-processes to transform the test into our final outcome. That makes it possible to do a positive identification of a state and then negate that state, which is often easier to do than to test for the negative outcome itself.

Pre-processes can be attached to both subjects and expectations, which gives slightly different testing styles. If the chain is started by subject then the pre-processes attach to the subject, while if it starts with expect then the pre-processes attach to the expectation. It is possible to start attaching to one of them and then shift to the other one.

Picks

Functions and methods may return multiple values, and there are a number of methods to extract a specific value from a list of values.

A few examples on methods for picking values

expect('a', 'b', 'c') :first() -- gives 'a'
expect('a', 'b', 'c') :second() -- gives 'b'

TODO: It should be possible to give an optional type argument to the methods.

TODO: There should be additional get(idx) calls, with negative indexes counting backwards.

Transforms

There are methods to transform (or shift) from one form to another form. Most of them only change form and keep the same type, but some also change the type. They are more commonly called cast operators in other programming languages.

A few examples on methods for casting types between strings and numbers

subject( '42' ) :asNumber() :toBe( 42 ) subject( 42 ) :asString() :toBe( '42' )

TODO: There should be methods for casting types between strings and localized numbers and dates.

TODO: Add description of type and reverse.

Conditions

There are methods to test for specific conditions. They can take their values from subject and expect, or given explicit values. First value masquerades as subject unless already found, while second value masquerade as expect unless already found. Found values are not counted on the argument list, so found subject would give expect as first value.

TODO: Verify this. A few examples on methods for conditions

subject = '42' expect :toBeEqual( '42' ) expect( 42 ) :toBeEqual()

generated by LDoc 1.4.6