Test Fixtures for Rack  Unit
1 Overview of Collections and Modules
2 Data Model
fixture
fixture?
fixture-initialized?
define-fixture
fixture-value
call/  fixture
fixture-name
fixture-info
3 Rack  Unit Integration
test-begin/  fixture
test-case/  fixture
7.0

Test Fixtures for RackUnit

Jack Firth <[email protected]>

 (require fixture) package: fixture

This library defines fixtures, resources used in test cases that are automatically created and destroyed at the beginning and end of each test case. Fixtures are built on top of rackunit test cases and the disposable library; familiarity with the two is assumed in this document.

(define-fixture tmpdir (disposable-directory))
(define-fixture tmpfile (disposable-file))
 
(test-case/fixture "tests"
  #:fixture tmpdir
  #:fixture tmpfile
  (test-case "some-test"
    ... use (current-tmpdir) and (current-tmpfile) ...)
  (test-case "other-test"
    ... use different (current-tmpdir) and (current-tmpfile) ...))

Source code for this library is available on Github and is provided under the terms of the Apache License 2.0.

Warning! This library is experimental; it may change in backwards incompatible ways without notice. As such, now is the best time for feedback and suggestions so feel free to open a repository issue or reach out to me directly.

1 Overview of Collections and Modules

This package provides several modules, all in the fixture collection:

2 Data Model

 (require fixture/base) package: fixture

A fixture is an external resource that must be properly initialized and disposed of for a test. Fixtures are essentially a pair of a disposable defining the external resource and a parameter that is set for each test to an instance of the disposable.

Additionally, fixtures may have fixture info; custom metadata about the current value of the fixture that can be used in test failure messages. Each fixture defines what info values it provides and there are no restrictions on the kind of values a fixture may use for info, although it’s expected that calling write on them produces something relatively useful.

procedure

(fixture name disp [#:info-proc info-proc])  fixture?

  name : symbol?
  disp : disposable?
  info-proc : (-> any/c any/c) = values
Returns a fixture named name that provides instances of values created with disp. The info-proc defines the fixture’s info, and is called with the current value of the fixture to when fixture-info is called. Fixtures must be initialized with call/fixture before use; attempting to access the current value before initialization raises a contract error.

Examples:
> (define (example-info n) (format "example value of ~v" n))
> (define ex
    (fixture 'ex example-disposable #:info-proc example-info))
> (call/fixture ex
    (thunk
     (displayln (fixture-value ex))
     (displayln (fixture-info ex))))

Allocated 66

66

example value of 66

Deallocated 66

> (fixture-value ex)

fixture-value: contract violation

  expected: fixture-initialized?

  given: #<fixture>

  in: an and/c case of

      the 1st argument of

      (->

       (and/c fixture? fixture-initialized?)

       any/c)

  contract from: <pkgs>/fixture/base.rkt

  blaming: program

   (assuming the contract is correct)

  at: <pkgs>/fixture/base.rkt:14.3

procedure

(fixture? v)  boolean?

  v : any/c
Returns #t if v is a fixture, returns #f otherwise.

procedure

(fixture-initialized? fix)  boolean?

  fix : fixture?
Returns #t if fix is currently initialized with call/fixture, returns #f otherwise.

syntax

(define-fixture id disposable-expr fixture-option ...)

 
fixture-option = #:accessor-id accessor-id
  | #:info-proc info-proc-expr
 
  disposable-expr : disposable?
  info-proc-expr : (-> any/c any/c)
Binds id to a fixture with name 'id, with disposable disposable-expr, and with its fixture info defined by info-proc-expr. Additionally, binds accessor-id to a shorthand function that call fixture-value on id. If accessor-id is not provided, it defaults to current-id. Each fixture-option may only be provided once.

Examples:
> (define (example-info n) (format "example value of ~v" n))
> (define-fixture ex example-disposable #:info-proc example-info)
> (call/fixture ex
    (thunk
     (displayln (current-ex))
     (displayln (fixture-info ex))))

Allocated 42

42

example value of 42

Deallocated 42

> (current-ex)

raise-argument-error: contract violation

  expected: exact-nonnegative-integer?

  given: "fixture"

  argument position: 3rd

  other arguments...:

   'current-ex

   "fixture not initialized"

   #<fixture>

procedure

(fixture-value fix)  any/c

  fix : (and/c fixture? fixture-initialized?)
Returns the current value of fix, or #f if the fixture has not been initialized.

procedure

(call/fixture fix proc)  any

  fix : fixture?
  proc : (-> any)
Initializes fix to a new instance of the fixture’s disposable within the body of proc, disposing of the instance of the fixture after calling proc. Returns whatever values are returned by proc. Within the dynamic extend of proc, fixture-initialized? returns #t. Multiple uses of call/fixture may be nested, but a nested use initializes fix to a different instance of disp.

Examples:
> (define-fixture ex example-disposable)
> (call/fixture ex (thunk (* (current-ex) (current-ex))))

Allocated 72

Deallocated 72

5184

procedure

(fixture-name fix)  symbol?

  fix : fixture?
Returns the name of fix.

procedure

(fixture-info fix)  any/c

  fix : (and/c fixture? fixture-initialized?)
Returns fix’s current fixture info by applying fix’s fixture info procedure to the current value of the fixture.

Examples:
> (struct example-info (value) #:transparent)
> (define-fixture ex example-disposable #:info-proc example-info)
> (call/fixture ex (thunk (fixture-info ex)))

Allocated 4

Deallocated 4

(example-info 4)

3 RackUnit Integration

 (require fixture/rackunit) package: fixture

syntax

(test-begin/fixture fixture-clause ... body ...+)

 
fixture-clause = #:fixture fixture-expr
 
  fixture-expr : fixture?
Like test-begin, but with support for fixtures. Within the given body forms, current-test-case-around is parameterized to a function that wraps the test in a call/fixture expression once for each fixture-expr. Every test found in the body forms, including the outer test-begin, is allocated its own instance of each fixture. Fixtures are allocated in order from top to bottom and deallocated in reverse.

Examples:
> (define-fixture ex1 example-disposable)
> (define-fixture ex2 example-disposable)
> (define (ex-sum) (+ (current-ex1) (current-ex2)))
> (test-begin/fixture
    #:fixture ex1
    #:fixture ex2
    (displayln (ex-sum))
    (test-case "nested" (displayln (ex-sum))))

Allocated 11

Allocated 0

11

Allocated 23

Allocated 39

62

Deallocated 39

Deallocated 23

Deallocated 0

Deallocated 11

Additionally, test failures are augmented with a check-info with the name 'fixtures. The info’s value is a nested-info containing one check info for each fixture used; that info’s name and value correspond to the fixture’s name and its fixture info at the time the test failure occurred.

Examples:
> (define-fixture file1 (disposable-file))
> (define-fixture file2 (disposable-file))
> (test-begin/fixture
    #:fixture file1
    #:fixture file2
    (check-equal? 1 2))

--------------------

FAILURE

fixtures:

  file1:      #<path:/var/tmp/rkttmp15327587751532758779449>

  file2:      #<path:/var/tmp/rkttmp15327587791532758779443>

name:       check-equal?

location:   eval:3:0

actual:     1

expected:   2

--------------------

syntax

(test-case/fixture name fixture-clause ... body ...+)

 
name = string-literal
     
fixture-clause = #:fixture fixture-expr
 
  fixture-expr : fixture?
Like test-begin/fixture, but for test-case instead of test-begin.