You have a bug in your view that only shows up sometimes, under as-yet-undetermined conditions, but the problem is that this view is part of a specific workflow in the application, so you have to get to the right page, fill out a form and click through a couple of dialogs before you can even see if the view is displaying correctly. If not, it’s back to tweaking the code to introduce a potential fix, then reload the application, and start over to get to the view again. Does this sound familiar?
Like any other code, views can have bugs. However, they’re often tested manually, which means that you have to keep going through the same steps over and over to test changes. Tedious.
Fortunately, as of version 1.2.0, elm-explorations/test
includes a few new modules which allow us to test view code and hopefully avoid the need for some of the laborious manual testing. (These modules have been migrated from a separate package, eeue56/elm-html-test
, by Aaron VonderHaar - thanks Aaron!)
Let’s look at a couple of simple examples. Suppose that we have a login form view written like this:
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
type Msg
= Login
viewLogin : Html Msg
viewLogin =
div [ class "login" ]
[ input [ placeholder "Organisation", value "" ] []
, input [ placeholder "User name", value "" ] []
, input
[ placeholder "Password"
, type_ "password"
, value ""
]
[]
, button [ onClick Login ] [ Html.text "Login" ]
]
We can test that this function correctly generates the login button in this manner:
import Test.Html.Query as Query
import Test.Html.Selector as Selector
-- ...
test "Login form has a Login button" <|
\() ->
viewLogin
|> Query.fromHtml
|> Query.find [ Selector.tag "button" ]
|> Query.has [ Selector.text "Login" ]
This is just a regular test that you would include in your test suite that’s exercised by elm-test
.
First, we have to transform the Html
value using Query.fromHtml
. Then, we find the relevant element using Query.find
, and finally check that its text is “Login”. The Query
module provides a number of other functions.
Another test could ensure that the password field has the type
attribute set to “password” (so that it doesn’t accidentally get replaced with a text field):
test "Password field is of the right type" <|
\() ->
viewLogin
|> Query.fromHtml
|> Query.find [ Selector.attribute <| type_ "password" ]
|> Query.has [ Selector.tag "input" ]
We can also test that clicking the login button produces the Login
message (after importing Test.Html.Event
):
import Test.Html.Event as Event
import Test.Html.Query as Query
import Test.Html.Selector as Selector
test "Login form generates the message" <|
\() ->
viewLogin
|> Query.fromHtml
|> Query.find [ tag "button" ]
|> Event.simulate Event.click
|> Event.expect Login
In this case, we have to find the button first (with Query.find
), then simulate a click event on it with Event.simulate Event.click
, and then verify that it results in the Login
message. The Event
module provides functions for simulating a number of other interactions.
In my book, Practical Elm, I talk a lot more about testing. You can learn how to write unit tests and property/fuzz tests with elm-test
, and how to test your update
function with random lists of messages. Actually, there’s a whole chapter on preventing and finding bugs, so for example you can learn how to leverage the type system to that end and how to use the time-travelling Elm debugger. Check it out!