Explicit context argument syntax for test
Andrew Tropin 30 May 2026 03:20 UTC
I've been dealing with a bit more complex tests lately and realized
there is no way to pass the context to the test itself.
One of the use cases is test requiring external entities to be present,
and some repitetive setup/teardown done for them.
To avoid repeating yourself, one can provide suite or test level
fixtures. Fixtures are easy to implement on test-runner side, they are
just functions in metadata.
This is how it works right now:
--8<---------------cut here---------------start------------->8---
(define current-db-connection
(make-parameter #f))
(define (dynamic-db-fixture f)
(lambda ()
(define db-connection #f)
(dynamic-wind
(lambda ()
(set! db-connection
(creat-db-connection "127.0.0.1:7777")))
(lambda ()
(parameterize ((current-db-connection db-connection))
(f)))
(lambda ()
(close-connection! db-connection)
(set! db-connection #f)))))
(define (dynamic-admin-data-fixture f)
(lambda ()
(dynamic-wind
(lambda ()
(init-admin-data-in-db! (current-db-connection)))
(lambda ()
(f))
(lambda ()
(delete-admin-data-from-db! (current-db-connection))))))
(define-suite my-dynamic-db-tests
'metadata
`((fixtures . (,dynamic-db-fixture)))
(test "db initialized"
(is (primary-tables-exist? (current-db-connection))))
(test "admin permissions"
'metadata
`((fixtures . (,dynamic-admin-data-fixture)))
(is (check-admin-permissions (current-db-connection)))))
--8<---------------cut here---------------end--------------->8---
The problem is that we need to rely on dynamic variables to make them
useful.
One way to improve the situation is to make test procedures to be one
argument function instead of being thunks. So resulting code will look
like:
--8<---------------cut here---------------start------------->8---
(define (explicit-db-fixture f)
(lambda (ctx)
(define db-connection #f)
(dynamic-wind
(lambda ()
(set! db-connection
(creat-db-connection "127.0.0.1:7777")))
(lambda ()
(f (append
`((db . ,db-connection))
ctx)))
(lambda ()
(close-connection! db-connection)
(set! db-connection #f)))))
(define (explicit-admin-data-fixture f)
(lambda (ctx)
(dynamic-wind
(lambda ()
(init-admin-data-in-db! (assoc-ref ctx 'db)))
(lambda ()
(f ctx))
(lambda ()
(delete-admin-data-from-db! (assoc-ref ctx 'db))))))
(define-suite my-explicit-argument-db-tests
'metadata
`((fixtures . (,explicit-db-fixture)))
(test ("db initialized" ctx)
(is (primary-tables-exist? (assoc-ref ctx 'db))))
(test ("admin permissions" ctx)
'metadata
`((fixtures . (,explicit-admin-data-fixture)))
(define db (assoc-ref ctx 'db))
(is (check-admin-permissions db))))
--8<---------------cut here---------------end--------------->8---
At first the difference can seem subtle, but the important part that
test runner gets a direct communication channel with tests and can
provide all necessary data to tests, fixture's values, callbacks,
metadata, etc.
Under the hood we can change the test body from a thunk to a
one-argument procedure. A runner would execute a test as:
((assoc-ref test 'test/body-procedure) context)
instead of:
((assoc-ref test 'test/body-thunk))
I've considered a few ways to implement context argument syntax and
ended up with the following:
(test ("uses runner services" ctx)
'metadata '((a . b))
(is (ok? ctx))
(is #t))
For test that doesn't need context, one can do:
(test ("description" _) ...)
It will break a few current users of suitbl library, but we can make a
deprecation warning and remove the old syntax in the next release.
Also, the new syntax makes the destinction between suite and test
clearer.
(suite "descr" body ...) immediately evaluates its body.
(test ("descr" arg) body ...) wraps the body into one-argument
procedure and gives it to a test runner.
--
Best regards,
Andrew Tropin