Use elm-spec to write specs that describe the behavior of your Elm program.
You think TDD/BDD is great, and maybe you're already writing tests in Elm or using tools like Cypress to test your program end-to-end. Why use elm-spec?
view
or update
functions, and it lets the
Elm runtime handle commands and subscriptions; your program interacts with the Elm runtime just as it
would in production.In short, with elm-spec you get the confidence you would from a browser-based end-to-end testing tool like Cypress, without losing the convenience of elm-based testing tools. You can still write your specs in Elm and you can still test parts of your code in isolation, but your specs run in a browser and they exercise your code just like it will be exercised in production.
Create a directory called specs
for your specs and change into that directory.
Initialize a new elm app with elm init
. Add your program's source
directory to the source-directories
field of elm.json
.
Install elm-spec: elm install brian-watkins/elm-spec
.
Install any other dependencies your app needs.
Add a file called Runner.elm
to your specs src directory. It should look something like this:
port module Runner exposing
( program
, browserProgram
, skip
, pick
)
import Spec exposing (Message)
port elmSpecOut : Message -> Cmd msg
port elmSpecIn : (Message -> msg) -> Sub msg
port elmSpecPick : () -> Cmd msg
config : Spec.Config msg
config =
{ send = elmSpecOut
, listen = elmSpecIn
}
pick =
Spec.pick elmSpecPick
skip =
Spec.skip
program =
Spec.program config
browserProgram =
Spec.browserProgram config
You must create the elmSpecOut
and elmSpecIn
ports and provide them to Spec.program
or Spec.browserProgram
via a Spec.Config
value.
You must also create the elmSpecPick
port and provide it to Spec.pick
.
Now you can write spec modules. Each spec module is an elm program and so must have a main
function. To construct
the main
function, just reference program
or browserProgram
from your Runner.elm
and
provide a List Spec
to run.
During the course of development, it's often useful to run only certain scenarios.
In that case, use pick
from your Runner.elm
to designate those scenarios. See the docs for Spec.pick
for more information.
You can also skip scenarios, if you like, by using Spec.skip
.
Here's an example spec module:
module SampleSpec exposing (main)
import Spec exposing (..)
import Spec.Setup as Setup
import Spec.Markup as Markup
import Spec.Markup.Selector exposing (..)
import Spec.Markup.Event as Event
import Spec.Claim as Claim
import Runner
import Main as App
clickSpec : Spec App.Model App.Msg
clickSpec =
describe "an html program"
[ scenario "a click event" (
given (
Setup.initWithModel App.defaultModel
|> Setup.withUpdate App.update
|> Setup.withView App.view
)
|> when "the button is clicked three times"
[ Markup.target << by [ id "my-button" ]
, Event.click
, Event.click
, Event.click
]
|> it "renders the count" (
Markup.observeElement
|> Markup.query << by [ id "count-results" ]
|> expect (
Claim.isSomethingWhere <|
Markup.text <|
Claim.isStringContaining 1 "You clicked the button 3 time(s)"
)
)
)
]
main =
Runner.browserProgram
[ clickSpec
]
To run your specs, you need to install a runner. There are currently two options.
You can run your specs in JSDOM or a real browser, right from the command line.
$ npm install --save-dev elm-spec-runner
Then, assuming your specs are in a directory called ./specs
, just run your spec suite like so:
$ npx elm-spec
By default, elm-spec-runner will execute your specs in a JSDOM environment. You can configure elm-spec-runner to execute your specs in a real browser via a command line option; chromium, webkit, and firefox are all available.
See elm-spec-runner for more details on command line options.
You can also run your specs in a real browser via Karma.
See karma-elm-spec-framework for more details.
For more examples, see the docs for elm-spec. In particular, there are examples demonstrating how to describe behavior related to HTTP requests, describe behavior related to ports, observe navigation changes, control time during a spec, select and work with files and downloads, and use witnesses to ensure one part of a program acts in an expected way.
For even more examples, see the specs for elm-spec.
For a real-world test suite, see the specs for a simple code-guessing game.
I suggest adding one more file to your spec suite: Spec/Extra.elm
.
module Spec.Extra exposing (equals)
import Spec.Claim as Claim exposing (Claim)
equals : a -> Claim a
equals =
Claim.isEqual Debug.toString
Then, you can import the equals
function from this module without having to write out
Claim.isEqual Debug.toString
every time.