On this page:
recspecs:   Expect Testing for Racket
expect
expect-file
expect-exn
expect-unreachable
capture-output
expectation
make-expectation
commit-expectation!
reset-expectation!
skip-expectation!
with-expectation
run-expect
run-expect-exn
update-file-entire
8.17.0.4

recspecs: Expect Testing for Racket🔗ℹ

 (require recspecs) package: recspecs-lib

The expect form captures anything printed to the current output port while evaluating an expression and compares it to a string literal stored directly in the source file. Each use expands to a RackUnit test-case. When the environment variable RECSPECS_UPDATE is set and the expectation does not match, the file is rewritten with the new output instead of failing the test. When RECSPECS_UPDATE is not set and the expectation fails, a colorized diff is printed to help understand the mismatch. Updating can be restricted to a single test case by setting RECSPECS_UPDATE_TEST to the name shown for that case.

Verbose mode can be enabled by setting RECSPECS_VERBOSE or by parameterizing recspecs-verbose?. When enabled, captured output is echoed to the real output port as it is produced. For example:

RECSPECS_VERBOSE=1 raco test my-test.rkt

For Emacs users, the accompanying "emacs/recspecs.el" file provides recspecs-update-at-point, which runs the current file under racket-test with those environment variables set for the expectation at the cursor position. After the test finishes the buffer is automatically reverted so that any updated expectations are reloaded from disk.

Use #:port 'stderr with expect, expect-file, expect-exn, or capture-output to record output written to the current error port instead of the output port. Pass 'both to capture from both ports simultaneously.

Output can be transformed before it is compared by parameterizing recspecs-output-filter. The parameter holds a procedure that receives the captured string and returns a new string used for the comparison and when updating:

(parameterize ([recspecs-output-filter
                (lambda (s) (regexp-replace* #px"[0-9]+" s ""))])
  (expect (display "v1.2") "v."))
(parameterize ([recspecs-output-filter string-upcase])
  (expect (display "ok") "OK"))
(parameterize ([recspecs-output-filter string-trim])
  (expect (display "  hi  ") "hi"))

The thunk that performs the test is executed via the procedure stored in recspecs-runner. The default simply calls the thunk, but it can be replaced to control the runtime context. For example, limit memory usage with a new custodian and redirect the error port:

(parameterize ([recspecs-runner
                (lambda (th)
                  (call-in-nested-thread
                   (lambda ()
                     (custodian-limit-memory (current-custodian) (* 1024 1024))
                     (parameterize ([current-error-port (current-output-port)])
                       (th)))))])
  (expect (begin
            (display "oops" (current-error-port))
            (make-bytes (* 2 1024 1024)))
          "oops"))

syntax

(expect expr expected-str ...)

Evaluates expr and checks that the captured output is equal to the concatenation of expected-strs. If they differ and RECSPECS_UPDATE is set, the expectation string in the source file is replaced with the new value. Otherwise the test case fails.

(require recspecs)
(expect (displayln "hello") "hello\n")

It can be convenient to use at-exp for multi-line expectations:

#:lang at-exp racket
(require recspecs)
 
(expect (begin (displayln "hello") (displayln (+ 1 2)))
"hello""\n"
"3")

syntax

(expect-file expr path-str)

Reads the expectation from path-str instead of embedding it in the source. The file is replaced with new output when RECSPECS_UPDATE is set.
(expect-file
  (begin
    (displayln "hello")
    (displayln "world"))
  "expected.txt")

syntax

(expect-exn expr expected-str ...)

Checks that expr raises an exception whose message matches the concatenation of expected-strs. The message is updated when update mode is enabled.
(expect-exn (raise-user-error "bad")
            "bad")

syntax

(expect-unreachable expr)

Fails the enclosing test if expr evaluates. When update mode is enabled, the form is replaced with expr in the source instead of failing.
(when #f
  (expect-unreachable (displayln "never")))

procedure

(capture-output thunk [#:port port])  string?

  thunk : (-> any/c)
  port : any/c = 'stdout
Runs thunk and returns everything printed to the selected port(s). When port is 'stderr, the current error port is captured instead of the output port. Pass 'both to capture from both ports. When recspecs-verbose? is true, the output is also echoed to the original port(s).

(capture-output (lambda () (display "hi")))

(capture-output (lambda () (display "err" (current-error-port)))
#:port 'stderr)
(capture-output (lambda ()
 (display "warn" (current-error-port))
 (display "out"))
#:port 'both)

struct

(struct expectation (out committed? skip?)
    #:extra-constructor-name make-expectation)
  out : string?
  committed? : boolean?
  skip? : boolean?
Represents recorded output that can be committed or skipped. The structure is mutable so repeated with-expectation blocks can append to out.

procedure

(make-expectation)  expectation?

Create a fresh expectation.

procedure

(commit-expectation! e)  void?

  e : expectation?
Mark e as committed.

procedure

(reset-expectation! e)  void?

  e : expectation?
Reset the output and flags of e.

procedure

(skip-expectation! e)  void?

  e : expectation?
Mark e as skipped.

syntax

(with-expectation e expr ...)

Evaluates the exprs and appends anything printed to e’s out field.

You can wrap any of the expectation forms with with-expectation and access the captured output with expectation-out:

(define log (make-expectation))
(with-expectation log
  (expect (display "hi") "hi"))
(commit-expectation! log)
(displayln (expectation-out log))

procedure

(run-expect thunk    
  expected    
  path    
  pos    
  span    
  [#:strict strict?    
  #:port port])  void?
  thunk : (-> any/c)
  expected : string?
  path : (or/c path-string? #f)
  pos : exact-nonnegative-integer?
  span : exact-nonnegative-integer?
  strict? : boolean? = #f
  port : (symbols 'stdout 'stderr 'both) = 'stdout
Runs thunk and checks that the captured output matches expected. The path, pos and span identify the source location used when updating.

procedure

(run-expect-exn thunk    
  expected    
  path    
  pos    
  span    
  [#:strict strict?    
  #:port port])  void?
  thunk : (-> any/c)
  expected : string?
  path : (or/c path-string? #f)
  pos : exact-nonnegative-integer?
  span : exact-nonnegative-integer?
  strict? : boolean? = #f
  port : (symbols 'stdout 'stderr 'both) = 'stdout
Like run-expect but expects thunk to raise an exception whose message matches expected.

procedure

(update-file-entire path pos span new-str)  void?

  path : path-string?
  pos : exact-nonnegative-integer?
  span : exact-nonnegative-integer?
  new-str : string?
Replace the entire file at path with new-str.