Next: (veritas model), Previous: (veritas generator), Up: API
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.
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)).
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)).
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)).
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))
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.
Return how many times spy was called.
Return #t when spy was called with exactly args.
Return the argument lists spy was called with, oldest first.
Return what spy returned, oldest first, one entry per call.
Return #t when value was made by make-spy or
make-stub.
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