Test doubles

There are two variants of test doubles that can be made; a stub function and a mock object. The stub function is a function that play back a sequence of provided values, and the mock object does the same with addition of verifying the calling method.

A third type is a fake object. This is an existing object that is changed so it can be mocked. It is questionable at best whether this really is a valid version of the preexisting object, and the state of the preexisting object surely is questionable after bein abused like this.

Usage

All types of test doubles are created using the same basic principle. A number of tables are collected as arguments, and when the test double are called the content is unpacked and returned as the current value.

It is not an error within the tested code when the sequence is exhausted, it is an error within the fixture code.

A stub can hold a reference to a creator function, and when called return a mock object. For example, this could be done to override the mw.getCurrentFrame and get a mock insted of the usual frame, thus making it possible to do deep code test. Such overrides must be done before the tested lib is loaded.

mw.pickle.install()
local currentFunc = stub( mock( … ) )
mw.getCurrentFrame = currentFunc
describe( …

Leaving a stub on a system call like that can create unexpected errors. If several code snippets call mw.getCurrentFrame, then the sequence will ultimately go empty and the stub rise an error.

Usually the construct work quite well, as the code in debug console is completely rerun on each invocation.

Stub function

The stub function is a convenience function for creating a closure holding references to the provided predefined vararg list of table wrapped values. For each call the next set of values will be returned. The function can thus return a list of values, even if the most common use case is a single value. The values are wrapped up when the stub is made

local test = stub( { 'foo' }, { 'bar' }, { 'baz' }, string.upper )
test( 'ping' ) -- precomputed value, returns 'foo'
test( 'ping' ) -- precomputed value, returns 'bar'
test( 'ping' ) -- precomputed value, returns 'baz'
test( 'ping' ) -- exhaused, but uses the onempty fallback, returns 'PING'

Code 1: Simple use of the convenience function.

When the convenience function is created some default housekeeping is done, like setting the error report level and setting a default name. No fallback is set for the convenience function.

Note that the convenience function is only available after the library is initialized.

Other forms are possible by using the double class, building an instance, and then turning it into a stub

local test = mw.pickle.double:create( 'first-test',
  { 'foo' }, { 'bar' }, { 'baz' } )
  :stub()      -- build and return closure
test( 'ping' ) -- precomputed value, returns 'foo'
test( 'ping' ) -- precomputed value, returns 'bar'
test( 'ping' ) -- precomputed value, returns 'baz'
test( 'ping' ) -- exhaused, and a call will rise an error

Code 2: Instance creation with only precomputed values, without fallback function. The code use vararg style.

local test = mw.pickle.double:create 'second-test'
  { 'foo' } { 'bar' } { 'baz' }
  :setOnEmpty( string.upper ) -- adds a fallback
  :stub()      -- build and return closure
test( 'ping' ) -- precomputed value, returns 'foo'
test( 'ping' ) -- precomputed value, returns 'bar'
test( 'ping' ) -- precomputed value, returns 'baz'
test( 'ping' ) -- exhaused, but uses the fallback, returns 'PING'

Code 3: Instance creation with precomputed values, with fallback function. The code use call chain style.

local test = mw.pickle.double:create()
  :setName( 'third-test' )
  :add ( true )         -- must wrap true literal
  :add { 'bar' }        -- value list
  :add { 'baz' }        -- value list
  :setOnEmpty( string.upper ) -- adds a fallback
  :stub()      -- build and return closure
test( 'ping' ) -- uses the fallback, returns 'PING'
test( 'ping' ) -- precomputed value, returns 'bar'
test( 'ping' ) -- precomputed value, returns 'baz'
test( 'ping' ) --exhaused, but uses the fallback, returns 'PING'

Code 4: Instance creation with precomputed values, with fallback function and in-list fallback. The code use method chain style.

It is possible to create similar functions as the provided stub convenience function. The function is noting more than

function stub( ... )
  return mw.pickle.double:create()
    :setLevel(2)
    :setName('stub')
    :dispatch( ... )
    :stub()
end

Code 5: Creation of a convenience function like stub.

In a real stub function it could be necessary to change the reporting level. This is the actual stack level to report. It could also be convenient to make a name that reflects the actual function name.

Mock object

The mock object…

Fake object

The fake object…

generated by LDoc 1.4.6