Elm-test-tables; a collection of useful elm-test extensions

Hello Elm,

I wrote something for you. Or actually, for myself, over the last 6+ months. I thought I’d share it, because I think you’ll find it useful.


Fuzzer comparable

I’ve felt for quite a while now that I’d like to make my tests read like what they’re trying to test, rather than how they’re testing it. For example, using fuzz (list int) when you really want a fuzz (list comparable) or a fuzz (list a). Elm-test doesn’t expose a comparble fuzzer, and I’m fairly convinced that it shouldn’t, but I still want it for my tests.

fuzz (list int) "reversing a list twice results in the original list" <|
    \lst -> lst |> List.reverse |> List.reverse |> Expect.equal lst

changes into:

fuzz (list a) "reversing a list twice results in the original list" <|
    \lst -> lst |> List.reverse |> List.reverse |> Expect.equal lst

Flaky Fuzzers and Edge-cases

Another problem I’ve faced too many times is that fuzzers aren’t perfect; they just don’t always find all edge-cases, and as a result, there might be regressions, because we rely on fuzz tests to find all edge-cases for us. To combat this, I wrote a function with the same api, that takes a list of edge cases, runs those first, and then the normal fuzzer.

fuzz int "input is not equal to 11147" <|
    \a -> a |> Expect.notEqual 11147

turns into

fuzzTable int "input is not equal to 11147" [ 11147 ] <|
    \a -> a |> Expect.notEqual 11147

JSON Fuzzers

We all use json, and it’s fairly easy to get the encoders/decoders right, but every once in a while something sneaks in. Thus, I started writing tests for every single json encoder/decoder I had, and this is a wonderful example of where fuzz tests can shine, if only there was a small helper function:

roundtrip "int encode/decode" Fuzz.int Json.Encode.int Json.Decode.int

With this function, we’ll generate random values, encode them, decode them, and check that there was no data loss along the way.

Proper tests for T.map and T.andThen

Many data structures supply map and andThen (a.k.a. concatMap), and we’ve all learned what they look like and how they should work. They’re actually Functors and Monads, and Functors and Monads have laws, that we expect map and andThen to obey. They’re just a bit tricky to express as tests in Elm, so I wrote some helper functions for them. The v1, v2, v3 are to make sure your map/andThen functions are not limited to a single type, and I had to split them into three functions to make it type check.

describe "List.map"
    [ mapv1 List.map Fuzz.list
    , mapv2 List.map Fuzz.list
    , mapv3 List.map Fuzz.list

describe "List.andThen a.k.a. List.concatMap"
    [ andThenv1 List.singleton List.concatMap Fuzz.list
    , andThenv2 List.singleton List.concatMap Fuzz.list
    , andThenv3 List.singleton List.concatMap Fuzz.list

Expect + Category theory

Wait, come back! It’s actually useful!

fuzz3 int int int "+" <|
    \a b c -> { f = (+), a = a, b = b, c = c }
        |> Expect.all [ identityElement 0, associative, commutative ]

This tests that the function +:

  • Has an identity element 0, which means that a + 0 = 0 + a = a
  • Is associative, which means that (a+b)+c = a+(b+c)
  • Is commutative, which means that a+b = b+a

Unique Fuzzers

Sometimes you just don’t want your inputs to equal each other. Good thing we have these fuzzers then.

fuzz (list Fuzz.Unique.int) "list of unique int" <| 
    \v -> v |> Set.fromList |> Set.size |> Expect.equal (List.length v)

or even

fuzz (list Fuzz.Opaque.Unique.comparable) "list of unique int" <| 
    \v -> v |> Set.fromList |> Set.size |> Expect.equal (List.length v)

But wait, there’s more

I’ve just outlined some of the things in this package. Go and have a look yourself, and let me know what you think!


Hope you find it useful!


This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.