Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

test.mock

import test.mock

test lifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelled test.mock.* needs import test.mock.

Test doubles: functions that record how they were called, and stand-ins that replace a real one for the length of a test.

A recording function

import test { * }

describe('retry', @{
  it('calls the operation once per attempt', @{
    var operation = mock()
    operation.raises_once(Error('first attempt'))
    operation.returns('ok')

    expect(retry(operation.fn, 3)).to_be('ok')
    expect(operation).to_have_been_called_times(2)
  })
})

mock() hands back a Mock, and mock.fn is the plain function to pass wherever a callback is wanted. Matchers accept either, so expect(operation) and expect(operation.fn) mean the same thing.

Replacing something that already exists

spy_on() swaps a function out in place and gives back a Mock that both records and stands in for it:

var clock = { now: @{ return real_time() } }
var now = spy_on(clock, 'now')
now.returns(1000)

It works on a dictionary entry and on an instance property that holds a function. It does not work on a class method or a module function: Zuri classes are immutable once declared, and a module’s members cannot be assigned from outside it. Code that wants to be substitutable takes its collaborators as arguments or holds them in properties, which is the shape worth designing for anyway.

Lifetime

The runner calls restore_all() after every test, so a spy never outlives the test that installed it, even one that failed partway through. Nothing needs to be undone by hand.

Functions

mock()

test.mock(implementation: ?function, name: ?string) -> Mock

A new recording function.

var send = mock()
notify(send.fn)
expect(send).to_have_been_called_with('hello')

Parameters

  • implementation (?function) — what it does when called. Without one it records the call and returns nil.
  • name (?string) — used in failure messages; 'mock' by default.

Returns Mock

spy_on()

test.spy_on(target, key: string) -> Mock

Replaces target[key] with a recording stand-in that calls through to the original, and returns the Mock that now sits there.

Calling through is the default on purpose: a spy that changes behaviour as well as observing it is a different tool, and saying so is one .returns() away.

var gateway = { charge: @(amount) { return real_charge(amount) } }
var charge = spy_on(gateway, 'charge')

checkout(gateway)

expect(charge).to_have_been_called_times(1)

Parameters

  • target (dict|instance) — a dictionary, or an instance with a property holding a function.
  • key (string)

Returns Mock

Raises TestSetupError when target is not a dictionary or an instance, when it has no such entry, or when that entry does not hold something callable. A class method cannot be spied on: classes are immutable once declared.

mock_of()

test.mock.mock_of(value)

The Mock behind value, which may be a Mock already or the plain function one handed out.

Parameters

  • value (any)

Returns — Mock: nil when value is neither.

restore_all()

test.mock.restore_all()

Undoes every spy and forgets every mock built so far.

The runner calls this after each test, so a spy installed by a test that failed halfway through still comes back out.

reset_all()

test.mock.reset_all()

Forgets the calls recorded by every live mock, leaving the mocks themselves and their behaviours in place.

Classes

Call

class test.mock.Call

One recorded call.

Fields

FieldTypeDescription
argsThe arguments, in order.
resultWhat the call returned, or nil when it raised.
errorThe error it raised, or nil when it returned.
threwWhether the call raised rather than returned.

Constructor

test.mock.Call(args)

Mock

class test.Mock

A recording stand-in for a function.

Built by mock() and by spy_on(); there is no reason to construct one directly.

Fields

FieldTypeDescription
nameWhat the mock is called in failure messages.
fnThe plain function to pass around.
callsEvery call so far, oldest first.

Constructor

test.Mock(name, implementation, replacing)

Parameters

  • name (?string)
  • implementation (?function) — what the mock does when called. A mock with none returns nil.
  • replacing (?dict) — for a spy, { target, key, original } naming what this mock was installed over, so restore() knows what to put back.

Mock.returns()

test.Mock.returns(value)

Makes every call from now on return value.

Parameters

  • value (any)

Returns — Mock: itself, so calls chain.

Mock.returns_once()

test.Mock.returns_once(value) -> Mock

Makes the next call return value, once. Queue several to script a sequence; once the queue runs dry the standing behaviour takes over again.

Parameters

  • value (any)

Returns Mock

Mock.raises()

test.Mock.raises(value) -> Mock

Makes every call from now on raise value.

Parameters

  • value (Error)

Returns Mock

Mock.raises_once()

test.Mock.raises_once(value) -> Mock

Makes the next call raise value, once.

Parameters

  • value (Error)

Returns Mock

Mock.implements()

test.Mock.implements(implementation: function) -> Mock

Makes every call from now on run implementation and return what it returns. The implementation receives exactly the arguments the mock was called with.

Parameters

  • implementation (function)

Returns Mock

Mock.implements_once()

test.Mock.implements_once(implementation: function) -> Mock

Makes the next call run implementation, once.

Parameters

  • implementation (function)

Returns Mock

Mock.clear_behaviour()

test.Mock.clear_behaviour() -> Mock

Restores the original behaviour, dropping the standing one and anything still queued. Recorded calls are kept.

Returns Mock

Mock.call_count()

test.Mock.call_count() -> int

How many times the mock has been called.

Returns int

Mock.called()

test.Mock.called() -> bool

Whether the mock has been called at all.

Returns bool

Mock.call_args()

test.Mock.call_args() -> list

The arguments of every call, as a list of lists.

Returns list

Mock.last_call()

test.Mock.last_call() -> Call

The most recent call, or nil when there has not been one.

Returns Call

Mock.nth_call()

test.Mock.nth_call(index: int) -> Call

The index-th call, counting from zero, or nil when there have not been that many. A negative index counts back from the most recent.

Parameters

  • index (int)

Returns Call

Mock.reset()

test.Mock.reset() -> Mock

Forgets every recorded call. The behaviour is left alone.

Returns Mock

Mock.restore()

test.Mock.restore() -> Mock

Puts back whatever this mock replaced, if it replaced anything.

Safe to call more than once, and on a mock that was never installed over something.

Returns Mock