elm-explorations/test/1.1.0

NOTE: the latest version is 2.2.1

Fuzz

This is a library of fuzzers you can use to supply values to your fuzz tests. You can typically pick out which ones you need according to their types.

A Fuzzer a knows how to create values of type a in two different ways. It can create them randomly, so that your test's expectations are run against many values. Fuzzers will often generate edge cases likely to find bugs. If the fuzzer can make your test fail, it also knows how to "shrink" that failing input into more minimal examples, some of which might also cause the tests to fail. In this way, fuzzers can usually find the smallest or simplest input that reproduces a bug.

Common Fuzzers

int : Fuzzer Int

A fuzzer for int values. It will never produce NaN, Infinity, or -Infinity.

It's possible for this fuzzer to generate any 32-bit integer, but it favors numbers between -50 and 50 and especially zero.

intRange : Int -> Int -> Fuzzer Int

A fuzzer for int values within between a given minimum and maximum value, inclusive. Shrunken values will also be within the range.

Remember that Random.maxInt is the maximum possible int value, so you can do intRange x Random.maxInt to get all the ints x or bigger.

float : Fuzzer Float

A fuzzer for float values. It will never produce NaN, Infinity, or -Infinity.

It's possible for this fuzzer to generate any other floating-point value, but it favors numbers between -50 and 50, numbers between -1 and 1, and especially zero.

floatRange : Float -> Float -> Fuzzer Float

A fuzzer for float values within between a given minimum and maximum value, inclusive. Shrunken values will also be within the range.

percentage : Fuzzer Float

A fuzzer for percentage values. Generates random floats between 0.0 and 1.0. It will test zero and one about 10% of the time each.

string : Fuzzer String

Generates random printable ASCII strings of up to 1000 characters.

Shorter strings are more common, especially the empty string.

bool : Fuzzer Bool

A fuzzer for boolean values. It's useful when building up fuzzers of complex types that contain a boolean somewhere.

We recommend against writing tests fuzzing over booleans. Write a unit test for the true and false cases explicitly.

maybe : Fuzzer a -> Fuzzer (Maybe a)

Given a fuzzer of a type, create a fuzzer of a maybe for that type.

result : Fuzzer error -> Fuzzer value -> Fuzzer (Result error value)

Given fuzzers for an error type and a success type, create a fuzzer for a result.

list : Fuzzer a -> Fuzzer (List a)

Given a fuzzer of a type, create a fuzzer of a list of that type. Generates random lists of varying length, favoring shorter lists.

array : Fuzzer a -> Fuzzer (Array a)

Given a fuzzer of a type, create a fuzzer of an array of that type. Generates random arrays of varying length, favoring shorter arrays.

Working with Fuzzers

type alias Fuzzer a =
Fuzzer a

The representation of fuzzers is opaque. Conceptually, a Fuzzer a consists of a way to randomly generate values of type a, and a way to shrink those values.

oneOf : List (Fuzzer a) -> Fuzzer a

Choose one of the given fuzzers at random. Each fuzzer has an equal chance of being chosen; to customize the probabilities, use frequency.

Fuzz.oneOf
    [ Fuzz.intRange 0 3
    , Fuzz.intRange 7 9
    ]
constant : a -> Fuzzer a

Create a fuzzer that only and always returns the value provided, and performs no shrinking. This is hardly random, and so this function is best used as a helper when creating more complicated fuzzers.

map : (a -> b) -> Fuzzer a -> Fuzzer b

Map a function over a fuzzer. This applies to both the generated and the shrunken values.

map2 : (a -> b -> c) -> Fuzzer a -> Fuzzer b -> Fuzzer c

Map over two fuzzers.

map3 : (a -> b -> c -> d) -> Fuzzer a -> Fuzzer b -> Fuzzer c -> Fuzzer d

Map over three fuzzers.

(a -> b -> c -> d -> e)
-> Fuzzer a
-> Fuzzer b
-> Fuzzer c
-> Fuzzer d
-> Fuzzer e

Map over four fuzzers.

(a -> b -> c -> d -> e -> f)
-> Fuzzer a
-> Fuzzer b
-> Fuzzer c
-> Fuzzer d
-> Fuzzer e
-> Fuzzer f

Map over five fuzzers.

andMap : Fuzzer a -> Fuzzer (a -> b) -> Fuzzer b

Map over many fuzzers. This can act as mapN for N > 5. The argument order is meant to accommodate chaining: map f aFuzzer |> andMap anotherFuzzer |> andMap aThirdFuzzer Note that shrinking may be better using mapN.

frequency : List ( Float, Fuzzer a ) -> Fuzzer a

Create a new Fuzzer by providing a list of probabilistic weights to use with other fuzzers. For example, to create a Fuzzer that has a 1/4 chance of generating an int between -1 and -100, and a 3/4 chance of generating one between 1 and 100, you could do this:

Fuzz.frequency
    [ ( 1, Fuzz.intRange -100 -1 )
    , ( 3, Fuzz.intRange 1 100 )
    ]

There are a few circumstances in which this function will return an invalid fuzzer, which causes it to fail any test that uses it:

  • If you provide an empty list of frequencies
  • If any of the weights are less than 0
  • If the weights sum to 0

Be careful recursively using this fuzzer in its arguments. Often using map is a better way to do what you want. If you are fuzzing a tree-like data structure, you should include a depth limit so to avoid infinite recursion, like so:

type Tree
    = Leaf
    | Branch Tree Tree


tree : Int -> Fuzzer Tree
tree i =
    if i <= 0 then
        Fuzz.constant Leaf


    else
        Fuzz.frequency
            [ ( 1, Fuzz.constant Leaf )
            , ( 2, Fuzz.map2 Branch (tree (i - 1)) (tree (i - 1)) )
            ]

Tuple Fuzzers

Instead of using a tuple, consider using fuzzN.

tuple : ( Fuzzer a, Fuzzer b ) -> Fuzzer ( a, b )

Turn a tuple of fuzzers into a fuzzer of tuples.

tuple3 : ( Fuzzer a, Fuzzer b, Fuzzer c ) -> Fuzzer ( a, b, c )

Turn a 3-tuple of fuzzers into a fuzzer of 3-tuples.

Uncommon Fuzzers

custom : Generator a -> Shrinker a -> Fuzzer a

Build a custom Fuzzer a by providing a Generator a and a Shrinker a. Generators are defined in elm/random, which is not core's Random module but has a compatible interface. Shrinkers are defined in eeue56/elm-shrink.

Here is an example for a record:

import Random
import Shrink


type alias Position =
    { x : Int, y : Int }


position : Fuzzer Position
position =
    Fuzz.custom
        (Random.map2 Position (Random.int -100 100) (Random.int -100 100))
        (\{ x, y } -> Shrink.map Position (Shrink.int x) |> Shrink.andMap (Shrink.int y))

Here is an example for a custom union type, assuming there is already a genName : Generator String defined:

type Question
    = Name String
    | Age Int


question =
    let
        generator =
            Random.bool
                |> Random.andThen
                    (\b ->
                        if b then
                            Random.map Name genName


                        else
                            Random.map Age (Random.int 0 120)
                    )


        shrinker question =
            case question of
                Name n ->
                    Shrink.string n |> Shrink.map Name


                Age i ->
                    Shrink.int i |> Shrink.map Age
    in
    Fuzz.custom generator shrinker

It is not possible to extract the generator and shrinker from an existing fuzzer.

char : Fuzzer Char

A fuzzer for char values. Generates random ascii chars disregarding the control characters and the extended character set.

unit : Fuzzer ()

A fuzzer for the unit value. Unit is a type with only one value, commonly used as a placeholder.

order : Fuzzer Order

A fuzzer for order values.

invalid : String -> Fuzzer a

A fuzzer that is invalid for the provided reason. Any fuzzers built with it are also invalid. Any tests using an invalid fuzzer fail.