Next: , Previous: , Up: API  


1.6 (veritas mock)

Test doubles: spies, stubs and temporary bindings, in the spirit of a mocking library. Scheme needs less of one than most languages do - a module’s procedures can be passed in, so a fake is often just a lambda - but recording what was called, and checking it after the fact, is worth having.

(define send (make-spy real-send)) ; records every call (define clock (make-stub #:returns 0)) ; ignores its arguments

(test "the receipt is sent once" (lambda (vc) (send "ada" "thanks") ; the call under test ((assert-called-with send ’("ada" "thanks")) vc) ((assert-call-count send #:expect 1) vc)))

The assertions read the spy when they run, not when they are built, which is why they are written as a case lambda: a spy’s calls are test-time state.

A stub can also stand in for a module binding without editing the module, as long as the call site reads that binding when it runs - see ‘with-temporary-binding’ for exactly when that is.

1.6.1 Procedures

Procedure: assert-call-count spy KEY: #:expect #:name

Assert that spy was called exactly expect times.

Checked when the assertion runs, so it is written as a case lambda: (lambda (vc) ((assert-call-count spy #:expect 2) vc)).

Procedure: assert-called spy KEY: #:name

Assert that spy was called at least once.

Checked when the assertion runs, so it is written as a case lambda: (lambda (vc) ((assert-called spy) vc)).

Procedure: assert-called-with spy args KEY: #:name

Assert that spy was called with exactly args.

Checked when the assertion runs, so it is written as a case lambda: (lambda (vc) ((assert-called-with spy '(1 2)) vc)).

Procedure: make-spy proc KEY: #:name

Return a procedure that calls proc and records every call.

The recording is read back with spy-calls, spy-results and spy-call-count. name is only used in the messages of the assert-* checks below.

(define double (make-spy (lambda (x) (* x 2))))
(double 3)
(spy-calls double)
⇒ ((3))
Procedure: make-stub KEY: #:returns #:name

Return a stand-in procedure that ignores its arguments and returns returns.

A stub is a spy whose procedure ignores its arguments, so spy-calls, spy-call-count and the assert-* checks work on it unchanged.

Procedure: spy-call-count spy

Return how many times spy was called.

Procedure: spy-called-with? spy args

Return #t when spy was called with exactly args.

Procedure: spy-calls spy

Return the argument lists spy was called with, oldest first.

Procedure: spy-results spy

Return what spy returned, oldest first, one entry per call.

Procedure: spy? value

Return #t when value was made by make-spy or make-stub.

Procedure: with-temporary-binding module var value thunk

Call thunk with module’s var set to value, and restore it.

The previous value comes back even when thunk raises, so substituting a dependency is safe to try.

Guile resolves a call to a known procedure while it compiles, so a compiled call site does *not* see the change: a call written in a module the compiler can see through keeps calling the original procedure. Measured on Guile 3.0, swapping a binding is observed by module-ref, by code passed to eval, and by call sites in a module declared #:declarative? #f; it is not observed by a call compiled in a declarative module, which is the default.

That leaves the ordinary Scheme answer, which needs no substitution at all: pass the dependency in, or hand the caller a make-spy recording. Use this when the code under test reaches the binding at run time.

(with-temporary-binding (resolve-module '(my app)) 'now
                        (lambda () 0)
                        (lambda ()
                          (assert-equal #:expect 0
                                        #:got (eval '((@ (my app) now))
                                                    (resolve-module '(my app))))))

Next: (veritas model), Previous: (veritas generator), Up: API