Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/brian-watkins/elmer

Describe the behavior of Elm HTML applications
https://github.com/brian-watkins/elmer

elm testing

Last synced: about 2 months ago
JSON representation

Describe the behavior of Elm HTML applications

Awesome Lists containing this project

README

        

# ELMER IS DEPRECATED

There will be no more updates to Elmer. It was fun while it lasted, but I'm pretty sure
Elmer won't work with Elm 0.19.1 or later versions, and I'm not planning to make any more updates.

## Instead, you should use [elm-spec](https://package.elm-lang.org/packages/brian-watkins/elm-spec/latest/).

Elm-spec is a test framework for Elm that lets you describe the behavior of Elm programs, much like Elmer did.
Unlike Elmer, elm-spec doesn't use kernel/native code so it has been published to the Elm package repository
and should be more resilient to future changes in Elm.

---------

# Elmer

Elmer makes it easy to describe the behavior of Elm HTML applications. If you love TDD and
you love Elm, then you'll probably appreciate Elmer.

### Why?

Behavior-driven development is a great practice to follow when writing
applications. If you describe the behavior of your app in code, it's easy to add
new features or refactor the code with confidence: just run the tests to see
if your app still has all the behavior you've described in those tests.

Elm is a really great language for writing web applications. However, practicing BDD
in Elm can be difficult. The two main functions in the Elm architecture -- `view` and
`update` -- return opaque types, which cannot be inspected for testing purposes.
Even so, calling `view` or `update` directly requires knowledge of an application's
implementation: the shape of its model, its messages, and so on. If writing
tests requires knowledge of implementation details, you lose the biggest
benefit of writing tests in the first place: the ability to change your code
with confidence.

Elmer allows you to describe the behavior of your app without knowledge of
implementation details. It simulates the Elm architecture, calling `view`
and `update` as necessary throughout the course of your test. It lets
you manage how commands and subscriptions are processed so you can
describe the behavior of your app under whatever conditions you need. Elmer
allows you to write tests first, which gives you the freedom and confidence
to change your code later on.

## Getting Started

Because Elmer uses some native Javascript code to accomplish its magic, you cannot install Elmer through the elm package repository. Instead, you can install Elmer with the `elmer-test` package on NPM. Follow these steps to TDD bliss ...

### Install

First, you'll need to install
- Elm (0.19)
- The latest version of the [node test runner](https://www.npmjs.com/package/elm-test)
for the elm test package that works with Elm 0.19 (`[email protected]`)
- This package, which you'll install via npm

I recommend installing these dependencies locally in your project directory so you can track versions carefully. Here's the command to install all these at once:

```
$ npm install --save-dev elm [email protected] elmer-test
```

Now install the elm test library:

```
$ npx elm install elm-explorations/test
```

### Update the elm.json file

In your `elm.json` file, you'll need to manually add elmer to the `test-dependencies` section like so:

```
"test-dependencies": {
"direct": {
"elm-explorations/test": "1.1.0",
"elm-explorations/elmer": "6.0.0"
},
"indirect": {}
}
```

The latest version of Elmer is 6.0.0. Make sure the version number of elmer
matches the version number of the `elmer-test` NPM package.

Notice the `indirect` section under `test-dependencies`. Elmer itself has the following dependencies:

```
"dependencies": {
"elm/browser": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/html": "1.0.0 <= v < 2.0.0",
"elm/json": "1.0.0 <= v < 2.0.0",
"elm/random": "1.0.0 <= v < 2.0.0",
"elm/url": "1.0.0 <= v < 2.0.0",
"elm-explorations/test": "1.0.0 <= v < 2.0.0"
},
```

If any of these dependencies are not already listed as direct or indirect dependencies of your app, you'll need to list these in the `indirect` section of your `test-dependencies`.

If you just try to run elm-test (see below) and you're missing any dependencies, the compiler will give you an error message. Take the missing dependencies it mentions and list them as indirect test dependencies.

### Run

Now that everything's in place, you're ready to write tests with Elmer. In order to run those tests, you'll need to set the `ELM_HOME` environment variable to the `home` directory under the `elmer-test` install. If you've installed `elmer-test` locally, the directory should look like this:

```
/node_modules/elmer-test/home
```

I recommend adding a test script to your `package.json` that sets the environment variable for you. The following will work on a Mac running bash:

```
"scripts": {
"test": "ELM_HOME=$(pwd)/node_modules/elmer-test/home elm-test"
}
```

Note that `ELM_HOME` must be an absolute path (thus the `$(pwd)` in the test command).

### Caveats

The `elm` command searches for test dependencies any time you invoke it (so, even if you aren't running tests). This means that you will need to set the `ELM_HOME` environment variable as described above, any time you invoke the `elm` command. For example, to build your app, you'll need to do something like:

```
$ ELM_HOME=$(pwd)/node_modules/elmer-test/home elm make src/Main.elm
```

## Releases

#### 6.0.0
- Removed `Elmer.Http` and the dependency on elm/http. `Elmer.Http` now lives
in its own [package](https://github.com/brian-watkins/elmer-http) so it can be updated independently.
- Provided new APIs useful for creating extensions and custom matchers. See `Elmer.Value`,
`Elmer.Message`, `Elmer.Message.Failure`,`Elmer.Effects`, and `Elmer.Task`

#### 5.0.1
- Support for calling a spy across multiple test states

#### 5.0.0
- Revised `Elmer.Spy` api to make it simpler and to allow the compiler to do type checking when injecting a spy or providing a fake implementation. This should provide better feedback when working with spies
- See the section at the end of this document on migrating tests from 4.0.0 to 5.0.x

#### 4.0.0
- Updated Elmer to work with Elm 0.19
- Revised api for targeting Html elements to allow the compiler to provide better feedback
- See the section at the end of this document on migrating tests from 3.3.1 to 4.0.0

#### 3.3.1
- Last version for Elm 0.18

## Documentation

Read the [latest documentation](https://elmer-test.cfapps.io/elmer).

If you're interested in Elmer for Elm 0.18, you should read the documentation for Elmer 3.3.1, which
you can find [here](https://elmer-test.cfapps.io/elmer/versions).

## Describing Behavior

While tests can be written with Elmer in a variety of ways, the goal is use Elmer to describe *behavior*
rather than implementation details. What do I mean? Let's say that an application is a collection of
behaviors. Each behavior has some pre-conditions -- these are characteristics of the world *outside* the
application that must be true for the behavior to occur -- and some set of resulting post-conditions --
characteristics of the world *outside* the application that are a consequence of the behavior. To
describe the *behavior* of an application, then, is to describe all the relationships that hold between
relevant states of the world outside the application due to the use of that application.

Here's an example of a behavior from some game application that displays high scores:

- Given that there is an HTTP web service that responds with a 200 status and a JSON document
that lists the high scores in some format.
- When the user starts the game application, then the high scores are displayed as list items in HTML.

This behavior links one state of the world -- where there is an HTTP web service that successfully
returns a JSON document in some known format -- and another -- where some HTML document contains
several `

  • ` elements whose text shows the high scores from the web service.

    To describe this behavior, we should not care how the application accomplishes the mapping between
    these two states. We only care about describing the two states. To make the test pass, we will need
    to provide some implementation, but the test gives us freedom to choose whatever implementation makes
    sense for us now. Most importantly, however, as we add new behaviors to our application, we will be
    able to refactor our code with confidence. No matter what implementation we end up with, we should
    still be able to run this test and ensure that the same mapping between pre-conditions and post-conditions
    still holds.

    Like I've said, you can use Elmer to write tests in a variety of ways, but I encourage you to
    write tests that describe *behavior* so that you can refactor your code with confidence later on. This means
    your tests should know as little as they can about the implementation of your Elm application. Strive to write
    tests that do not know the shape of your model or the particular messages that flow through the update function.
    Don't unit test functions. Begin each test only with references to the functions that must exist -- `view`, `update`,
    `init` -- and use Elmer to describe the pre- and post-conditions associated with some behavior.

    ### Create a TestState

    To begin a test with Elmer, you need to generate a `TestState` value. There are a variety of ways to do this:

    - Use `givenElement`, `givenApplication`, `givenDocument`, or `givenWorker` from
    the `Elmer.Program` module to test particular kinds of programs. In these cases, you'll
    use `Elmer.Program.init` to provide an initial model and command.
    - Use `Elmer.given` to test an arbitrary model, view method, and update method.
    - Use `Elmer.Command.given` to test a command-generating function in isolation.

    ### Working with HTML

    Since Elm is primarily designed for writing HTML applications, much of the work that goes into describing
    the pre- and post-conditions that characterize some behavior will involve working with HTML elements.

    Elmer allows you to simulate events on elements and examine the state of elements. In order to do either,
    you'll need to first target an element.

    #### Targeting an Element

    Use `Elmer.Html.target` along with the functions from `Elmer.Html.Selector` to target an element. Here's
    a partial test that targets all the `

  • ` elements that are children of an `
      ` with a class `scores`
      in the current view:

      ```
      allTests : Test
      allTests =
      describe "My Fun Game"
      [ describe "High Score Screen"
      [ test "it shows the high scores" <|
      \() ->
      Elmer.Program.givenElement App.view App.update
      |> Elmer.Program.init (\_ -> App.init testFlags)
      |> Elmer.Html.target
      << Elmer.Html.Selector.childrenOf
      [ Elmer.Html.Selector.tag "ol"
      , Elmer.Html.Selector.class "score-list"
      ]
      << Elmer.Html.Selector.by
      [ Elmer.Html.Selector.tag "li" ]
      ...
      ```

      See `Elmer.Html.Selector` for more examples of selectors. It's also possible to write your own.

      #### Taking action on an element

      Once you target an element, that element is the subject of subsequent actions, until
      you target another element. The following functions define actions on elements:

      + Click events: `Elmer.Html.Event.click `
      + Input events: `Elmer.Html.Event.input `
      + Custom events: `Elmer.Html.Event.trigger `
      + There are also events for mouse movements, and checking and selecting input elements. See
      the [docs](https://elmer-test.cfapps.io/elmer) for more information.

      #### Element Matchers

      You can make expectations about targeted elements with the `Elmer.Html.expect` function.

      First, specify whether you want to match against a single element (with `element`)
      or a list of elements (with `elements`).
      Then you provide the appropriate matchers for the element or the list. You can also
      expect that an element exists with the `elementExists` matcher.

      See `Elmer.Html.Matchers` for a full list of matchers. Let's add to the example above to make
      an expectation about the elements we targeted. We'll use `Elmer.expectAll` to chain together several
      assertions. We'll expect that the list of `

    1. ` we've targeted has 2 elements, with the first containing
      text of "700 Points" and the second "900 Points".

      ```
      allTests : Test
      allTests =
      describe "My Fun Game"
      [ describe "High Score Screen"
      [ test "it shows the high scores" <|
      \() ->
      Elmer.Program.givenElement App.view App.update
      |> Elmer.Program.init (\_ -> App.init testFlags)
      |> Elmer.Html.target
      << Elmer.Html.Selector.childrenOf
      [ Elmer.Html.Selector.tag "ol"
      , Elmer.Html.Selector.class "score-list"
      ]
      << Elmer.Html.Selector.by
      [ Elmer.Html.Selector.tag "li" ]
      |> Elmer.Html.expect (Elmer.Html.Matchers.elements <|
      Elmer.expectAll
      [ Elmer.hasLength 2
      , Elmer.atIndex 0 <| Elmer.Html.Matchers.hasText "700 Points"
      , Elmer.atIndex 1 <| Elmer.Html.Matchers.hasText "900 Points"
      ]
      )
      ]
      ]
      ```

      ### Commands

      Commands describe actions to be performed by the Elm runtime; the result of a command depends on the state of the
      world outside the Elm application. Elmer simulates the Elm runtime in order to
      facilitate testing, but it is not intended to replicate the Elm runtime's ability to carry out commands.
      Instead, Elmer allows you to specify what effect should result from running a command. This is one important
      way that Elmer allows you to describe the conditions that characterize an application behavior.

      #### Faking Effects

      Suppose there is a function `f : a -> b -> Cmd msg` that takes two arguments and produces a command. In order
      to specify the effect of this command in our tests, we will *override* occurrences of `f`
      with a function we create in our tests. This function will generate a special command
      that specifies the intended effect, and Elmer will process the result as if the original command were actually performed.

      For a more concrete example, check out this [article](https://medium.com/@brian.watkins/test-driving-elm-with-elmer-649e2e7e02a8), which discusses how to fake
      commands and subscriptions during a test.

      Note that while Elmer is not capable of processing any commands, it does support
      the general operations on commands in the core `Platform.Cmd` module, namely, `batch` and `map`. So, you
      can use these functions as expected in your application and Elmer should do the right thing.

      Elmer provides built-in support for navigation commands. If you want to work with Http during your tests, check
      out the [Elmer.Http](https://github.com/brian-watkins/elmer-http) extension.

      #### Elmer.Navigation

      Elmer provides support for functions in the [Browser.Navigation](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Navigation)
      module that allow you to handle navigation for single-page web applications.

      You'll need to begin your test with `Elmer.Program.givenApplication` since only Elm
      'application' programs can handle navigation. Provide a reference to the messages
      that handle new url requests and url changes along with the view and update functions. Then
      provide `Elmer.Spy.use` with `Elmer.Navigation.spy`
      so that Elmer will be able to record and process location updates by overriding
      `Browser.Navigation.pushUrl` and `Browser.Navigation.replaceUrl`.

      When you call `Elmer.Program.init` you'll need to use `Elmer.Navigation.fakeKey` to
      give your `init` function a `Browser.Navigation.Key` value. Here's an example of a test
      that expects the location to change when an element is clicked.

      ```
      Elmer.Program.givenApplication App.OnUrlRequest App.OnUrlChange App.view App.update
      |> Elmer.Spy.use [ Elmer.Navigation.spy ]
      |> Elmer.Program.init (\_ -> App.init testFlags testUrl Elmer.Navigation.fakeKey)
      |> Elmer.Html.target << by [ id "some-element" ]
      |> Elmer.Html.Event.click
      |> Elmer.Navigation.expectLocation "http://mydomain.com/funStuff.html"
      ```

      You can write an expectation about the current location with `Elmer.Navigation.expectLocation`.

      See `tests/src/Elmer/TestApps/NavigationTestApp.elm` and `tests/src/Elmer/NavigationTests.elm` for
      examples.

      #### Deferred Command Processing

      It's often necessary to describe the behavior of an application while some command is running. For example,
      one might want to show a progress indicator while an HTTP request is in process. Elmer provides
      general support for deferred commands. Use `Elmer.Command.defer` to create a command that
      will not be processed until `Elmer.resolveDeferred` is called. Note that all currently
      deferred commands will be resolved when this function is called.

      #### Testing Commands in Isolation

      You might want to test a command independently of any module that might use it. In that case,
      use `Elmer.Command.given` and provide it with a function that generates the command you
      want to test. This will initiate a `TestState` that simply records any messages that result when
      the given command is processed. You can use the `Elmer.Command.expectMessages` function to
      make any expectations about the messages received. For example, here's a test that expects a
      certain message when a certain command is processed:

      ```
      Elmer.Command.given (\_ -> MyModule.myCommand MyTagger withSomeArgument)
      |> Elmer.Command.expectMessages (\messages ->
      Expect.equal [ MyTagger "Fun Result" ]
      )
      ```

      You can use `Elmer.Command.given` with spies as it makes sense. So, you might
      write a test that exercises a module with some function that needs to be stubbed (like a port command):

      ```
      Elmer.Command.given (\_ -> MyModule.sendRequest MyTagger someArgument)
      |> Elmer.Spy.use [ someSpy ]
      |> Elmer.Command.expectMessages (\messages ->
      Expect.equal [ MyTagger "Fun Result" ]
      )
      ```

      ### Subscriptions

      Using subscriptions, your application can register to be notified when certain effects occur.
      To describe the behavior of an application that has subscriptions, you'll need to do these things:

      1. Override the function that generates the subscription using `Elmer.Spy.create` along with
      `Elmer.Spy.andCallFake`
      and replace it with a fake subscription using `Elmer.Subscription.fake`
      2. Register the subscriptions using `Elmer.Subscription.with`
      2. Simulate the effect you've subscribed to receive with `Elmer.Subscription.send`

      Here's an example test:

      ```
      timeSubscriptionTest : Test
      timeSubscriptionTest =
      describe "when a time effect is received"
      [ test "it prints the number of seconds" <|
      \() ->
      let
      timeSpy =
      Elmer.Spy.observe (\_ -> Time.every)
      |> Elmer.Spy.andCallFake (\_ tagger ->
      Elmer.Subscription.fake "timeEffect" tagger
      )
      in
      Elmer.given App.defaultModel App.view App.update
      |> Elmer.Spy.use [ timeSpy ]
      |> Elmer.Subscription.with (\() -> App.subscriptions)
      |> Elmer.Subscription.send "timeEffect" (Time.millisToPosix 3000)
      |> Elmer.Html.target << by [ id "num-seconds" ]
      |> Elmer.Html.expect (
      Elmer.Html.Matchers.element <|
      Elmer.Html.Matchers.hasText "3 seconds"
      )
      ]
      ```

      For a more complete example, check out this [article](https://medium.com/@brian.watkins/test-driving-elm-with-elmer-649e2e7e02a8).

      ### Ports

      You can manage ports during your test in just the same way you would manage any
      command or subscription.

      Suppose you have a port that sends data to Javascript:

      ```
      port module MyModule exposing (..)

      port sendData : String -> Cmd msg
      ```

      You can create a spy for this function just like you would for any command-generating
      function:

      ```
      Elmer.Spy.observe (\_ -> MyModule.sendData)
      |> Elmer.Spy.andCallFake (\_ -> Cmd.none)
      ```

      Note that you will need to provide a fake implementation of this method since
      otherwise Elmer will not know how to handle the generated command.

      A port that receives data from Javascript works just the same as any subscription.

      ```
      port receiveData : (String -> msg) -> Sub msg

      type Msg = ReceivedData String

      subscriptions : Module -> Sub Msg
      subscriptions model =
      receiveData ReceivedData
      ```

      We can create a spy for this subscription-generating function and provide a
      fake subscription that will allow us to send data tagged with the appropriate message
      during our test.

      ```
      let
      spy =
      Elmer.Spy.observe (\_ -> MyModule.receiveData)
      |> Elmer.Spy.andCallFake (\tagger ->
      Elmer.Subscription.fake "fake-receive" tagger
      )
      in
      Elmer.given MyModule.defaultModel MyModule.view MyModule.update
      |> Elmer.Spy.use [ spy ]
      |> Elmer.Subscription.with (\_ -> MyModule.subscriptions)
      |> Elmer.Subscription.send "fake-receive" "some fake data"
      |> ...
      ```

      ### Testing Tasks

      Elm uses tasks to describe asynchronous operations at a high-level. You can use Elmer
      to describe the behavior of applications that use the Task API. To do so:

      1. Stub any task-generating functions to return a task created with `Task.succeed`
      or `Task.fail` and the value you want as necessary for the behavior you want to describe.

      2. That's it.

      Elmer does not know how to run any tasks other than `Task.succeed` and `Task.fail`.
      However, Elmer does know how to properly apply all the functions from the Task API.
      In this way, Elmer allows you to describe the behavior that results from operations
      with tasks without actually running those tasks during your test.

      Here's an example. Suppose when a button is clicked, your app creates a task that gets
      the current time, formats it (using some function called `formatTime : Time -> String`),
      and tags the resulting string with `TagFormattedTime` like so:

      Time.now
      |> Task.map formatTime
      |> Task.perform TagFormattedTime

      You can test this behavior by replacing `Time.now` with a `Task.succeed`
      that resolves to the time you want.

      let
      timeSpy =
      Task.succeed (Time.millisToPosix 1515281017615)
      |> Spy.replaceValue (\_ -> Time.now)
      in
      testState
      |> Elmer.Spy.use [ timeSpy ]
      |> Elmer.Html.target << by [ id "get-current-time" ]
      |> Elmer.Html.Event.click
      |> Elmer.Html.target << by [ id "current-time" ]
      |> Elmer.Html.expect (
      element <| hasText "1/6/2018 23:23:37"
      )

      See the `Elmer.Task` module for more tasks that are useful when writing tests or
      building extensions to Elmer.

      ### Spies and Fakes

      Elmer generalizes the pattern for managing the effects of `Subs` and `Cmds`, allowing
      you to spy on any function you like. *NOTE* You should use Elmer spies sparingly and
      with care. Each spy that you add to your test couples that test to implementation details.

      Suppose you need to write a test that expects a certain function to be called, but
      you don't need to describe the resulting behavior. You can spy on a function with
      `Elmer.Spy.observe` and make expectations about it with `Elmer.Spy.expect`.

      For example, suppose you want to ensure that a component is calling a specific
      function in another module for parsing some string. You have tests for the
      parsing function itself; you just need to know that your component is using it.

      ```
      parseTest : Test
      parseTest =
      describe "when the string is submitted"
      [ test "it passes it to the parsing module" <|
      \() ->
      let
      spy =
      Elmer.Spy.observe (\_ -> MyParserModule.parse)
      |> Elmer.Spy.andCallThrough
      in
      Elmer.given App.defaultModel App.view App.update
      |> Elmer.Spy.use [ spy ]
      |> Elmer.Html.target << by [ tag "input", attribute ("type", "text") ]
      |> Elmer.Html.Event.input "A string to be parsed"
      |> Elmer.Spy.expect (\_ -> MyParserModule.parse) (
      wasCalled 1
      )
      ]
      ```

      Elmer also allows you to provide a fake implementation for any function.
      Suppose that you want to stub the result of the parsing function:

      ```
      parseTest : Test
      parseTest =
      describe "when the string is submitted"
      [ test "it displays the parsed result" <|
      \() ->
      let
      spy =
      Elmer.Spy.observe (\_ -> MyParserModule.parse)
      |> Elmer.Spy.andCallFake (\_ ->
      "Some Parsed String"
      )
      in
      Elmer.given App.defaultModel App.view App.update
      |> Elmer.Spy.use [ spy ]
      |> Elmer.Html.target << by [ tag "input", attribute ("type", "text") ]
      |> Elmer.Html.Event.input "A string to be parsed"
      |> Elmer.Html.target << by [ id "parsing-result" ]
      |> Elmer.Html.expect (Elmer.Html.element <|
      Elmer.Html.Matchers.hasText "Some Parsed String
      )
      ]
      ```

      For any spy, you can make an expectation about how many times it was called like so:

      ```
      Elmer.Spy.expect (\_ -> MyModule.someFunction) (wasCalled 3)
      ```

      You can also expect that the spy was called with some list of arguments at least once:

      ```
      Elmer.Spy.expect (\_ -> MyModule.someFunction) (
      Elmer.Spy.Matchers.wasCalledWith
      [ Elmer.Spy.Matchers.stringArg "someString"
      , Elmer.Spy.Matchers.anyArg
      , Elmer.Spy.Matcher.intArg 23
      ]
      )
      ```

      See `Elmer.Spy.Matchers` for a full list of argument matchers.

      `Elmer.Spy.observe` is good for spying on named functions in your production code.
      Sometimes, though, it would be nice to provide the code you are testing with a 'fake' function
      for testing purposes only. Suppose that you are testing a module that takes a function
      as an argument, and you want to expect that the function is called with a certain argument.
      You can create a 'fake' function in your test module, observe it with a spy, and provide it to
      the code you're testing using `Elmer.Spy.inject`. For example:

      ```
      someFake tagger data =
      Command.fake <| tagger data

      myTest =
      let
      spy =
      Spy.observe (\_ -> someFake)
      |> Spy.andCallThrough
      in
      Elmer.given testModel MyModule.view (MyModule.updateUsing <| Spy.inject (\_ -> someFake))
      |> Elmer.Spy.use [ spy ]
      |> Elmer.Html.target << by [ tag "input" ]
      |> Elmer.Html.Event.input "some text"
      |> Elmer.Html.target << by [ tag "button" ]
      |> Elmer.Html.Event.click
      |> Elmer.Spy.expect (\_ -> someFake) (
      Elmer.Spy.Matchers.wasCalledWith
      [ Elmer.Spy.Matchers.anyArg
      , Elmer.Spy.Matchers.stringArg "some text"
      ]
      )
      ```

      Use `Elmer.Spy.inject` to provide the function you want to observe so that Elmer has time to install the associated spy during the test.

      Finally, you can use `Spy.replaceValue` to replace the value returned by a no-argument function
      (such as `Time.now`) during a test. You can't make expectations about spies created in this
      way; `Spy.replaceValue` is just a convenient way to inject fake values during a test.

      ### Extensions

      It's easy to build extensions on top of Elmer to provide custom matchers or extra functions that
      help describe the behavior of an Elm application. In particular, `Elmer.Value`, `Elmer.Message`,
      and `Elmer.Effects` provide functions that are useful when writing custom matchers or extension
      modules.

      For a good example of an extension, see the [Elmer.Http](https://github.com/brian-watkins/elmer-http)
      package, which adds support for describing the behavior of apps that use HTTP.

      ### Upgrading from Elmer 5.x

      The `Elmer.Http` api has been removed and moved to its own [package](https://github.com/brian-watkins/elmer-http).

      `Elmer.Command.resolveDeferred` has moved to `Elmer.resolveDeferred` to accomodate the fact that Tasks can
      also be deferred.

      ### Upgrading from Elmer 4.x

      If you've written tests with Elmer 4.x, the `Elmer.Spy` api has changed:

      - `Elmer.Spy.create` is now `Elmer.Spy.observe` and no longer needs a string identifier.

      - When using `Elmer.Spy.observe` you must call either `Elmer.Spy.andCallThrough` or `Elmer.Spy.andCallFake` so
      that Elmer knows what to do when the observed function is called.

      - `Elmer.Spy.expect` now uses a reference to the observed function (rather than a string) to identify the function
      you'd like to assert about.

      - `Elmer.Spy.createWith` has been removed. Create a 'fake' function in your test module and observe it with `Elmer.Spy.observe` instead.

      - `Elmer.Spy.callable` has been removed. Instead, use `Elmer.Spy.inject` to provide a 'fake' function to the code under test.

      ### Upgrading from Elmer 3.x

      If you've written tests with Elmer 3.x and plan to upgrade them to Elmer 4.0.0, here are some things
      you'll need to consider:

      - `Elmer.Platform.Command` has changed to `Elmer.Command`.

      - `Elmer.Platform.Subscription` has changed to `Elmer.Subscription`.

      - `Elmer.Headless.givenCommand` has been replaced with `Elmer.Command.given`.

      - `Elmer.Headless.expectMessages` has been replaced with `Elmer.Command.expectMessages`.

      - `Elmer.Headless.given` has been replaced with `Elmer.Program.givenWorker`.

      - `Elmer.Http.expectThat` has been replaced with `Elmer.Http.expect`.

      - `Elmer.Http.expect` has been replaced with `Elmer.Http.expectRequest`.

      - The `<&&>` operator has been replaced with `Elmer.expectAll`.

      - `Elmer.Html.Matchers.hasProperty` has been removed. Use `Elmer.Html.Matchers.hasAttribute` instead.

      - `Elmer.Html.target` now has a new api. Combine functions in the `Elmer.Html.Selector` module to build a an element selector.

      ### Development

      To run the tests:

      ```
      $ cd tests
      $ ./test.sh
      ```