This is a fork from avh4's elm-testable, which is getting rewritten in Native code. This fork will keep being elm only.
This fork was upgraded to Elm 0.18 and has exciting new features, like testing your Html, querying, triggering events and so on.
This package allows you to write components that follow the Elm Architecture in a way that is testable.
To allow this, elm-testable provides testable versions of the Html
, Task
, Effects
, Http
and Process
modules,
as well as Testable.TestContext
to test testable components and Testable
to integrate testable components with your Elm app.
The only difference between a testable component and a standard component is the added Testable.
in several imports. (With the exception of Cmd
, which conflicts with the default import of Platform.Cmd
)
Here is the diff of converting RandomGif.elm
into a testable component:
diff --git a/examples/RandomGif.elm b/examples/RandomGif.elm
index 5ffd92e..85374a7 100644
--- a/examples/RandomGif.elm
+++ b/examples/RandomGif.elm
@@ -2,19 +2,21 @@ module RandomGif exposing (..)
--- From example 5 of the Elm Architecture Tutorial https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/05-http.elm
-import Html exposing (..)
-import Html.Attributes exposing (..)
-import Html.Events exposing (..)
-import Http
+import Testable.Html as Html exposing (..)
+import Testable.Html.Attributes exposing (..)
+import Testable.Html.Events exposing (..)
+import Testable.Http as Http
import Json.Decode as Decode
+import Testable
+import Testable.Cmd
main : Program Never Model Msg
main =
Html.program
- { init = init "cats"
- , view = view
- , update = update
+ { init = Testable.init (init "cats")
+ , view = Testable.view view
+ , update = Testable.update update
, subscriptions = subscriptions
}
@@ -29,7 +31,7 @@ type alias Model =
}
-init : String -> ( Model, Cmd Msg )
+init : String -> ( Model, Testable.Cmd.Cmd Msg )
init topic =
( Model topic "waiting.gif"
, getRandomGif topic
@@ -45,17 +47,17 @@ type Msg
| NewGif (Result Http.Error String)
-update : Msg -> Model -> ( Model, Cmd Msg )
+update : Msg -> Model -> ( Model, Testable.Cmd.Cmd Msg )
update msg model =
case msg of
MorePlease ->
( model, getRandomGif model.topic )
NewGif (Ok newUrl) ->
- ( Model model.topic newUrl, Cmd.none )
+ ( Model model.topic newUrl, Testable.Cmd.none )
NewGif (Err _) ->
- ( model, Cmd.none )
+ ( model, Testable.Cmd.none )
@@ -85,7 +87,7 @@ subscriptions model =
-- HTTP
-getRandomGif : String -> Cmd Msg
+getRandomGif : String -> Testable.Cmd.Cmd Msg
getRandomGif topic =
let
url =
Here is an example of the types of tests you can write for testable components:
all : Test
all =
describe "RandomGif"
[ test "sets initial topic"
<| \() ->
catsComponent
|> startForTest
|> find [ tag "h2" ]
|> assertText (Expect.equal "cats")
, test "sets initial loading image"
<| \() ->
catsComponent
|> startForTest
|> assertShownImage "waiting.gif"
, test "makes initial API request"
<| \() ->
catsComponent
|> startForTest
|> assertHttpRequest (Http.getRequest "https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=cats")
, test "pressing the button makes a new API request"
<| \() ->
catsComponent
|> startForTest
|> resolveHttpRequest (Http.getRequest "https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=cats")
(Http.ok """{"data":{"image_url":"http://giphy.com/cat2000.gif"}}""")
|> find [ tag "button" ]
|> trigger "click" "{}"
|> assertHttpRequest (Http.getRequest "https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=cats")
]
Here are complete tests for the RandomGif example.
You can also test that an outgoing port was called, by wrapping your ports with Testable.Cmd.wrap
, like this:
diff --git a/examples/Spelling.elm b/examples/Spelling.elm
index 7ae91ce..d8c335e 100644
--- a/examples/Spelling.elm
+++ b/examples/Spelling.elm
@@ -2,18 +2,20 @@ port module Spelling exposing (..)
-- From Elm Guide on JavaScript and Ports http://guide.elm-lang.org/interop/javascript.html
-import Html exposing (..)
-import Html.Attributes exposing (..)
-import Html.Events exposing (..)
+import Testable.Html as Html exposing (..)
+import Testable.Html.Events exposing (..)
+import Testable.Html.Attributes exposing (..)
import String
+import Testable.Cmd
+import Testable
main : Program Never Model Msg
main =
Html.program
- { init = init
- , view = view
- , update = update
+ { init = Testable.init init
+ , update = Testable.update update
+ , view = Testable.view view
, subscriptions = subscriptions
}
@@ -28,9 +30,9 @@ type alias Model =
}
-init : ( Model, Cmd Msg )
+init : ( Model, Testable.Cmd.Cmd Msg )
init =
- ( Model "" [], Cmd.none )
+ ( Model "" [], Testable.Cmd.none )
@@ -46,17 +48,17 @@ type Msg
port check : String -> Cmd msg
-update : Msg -> Model -> ( Model, Cmd Msg )
+update : Msg -> Model -> ( Model, Testable.Cmd.Cmd Msg )
update msg model =
case msg of
Change newWord ->
- ( Model newWord [], Cmd.none )
+ ( Model newWord [], Testable.Cmd.none )
Check ->
- ( model, check model.word )
+ ( model, Testable.Cmd.wrap <| check model.word )
Suggest newSuggestions ->
- ( Model model.word newSuggestions, Cmd.none )
+ ( Model model.word newSuggestions, Testable.Cmd.none )
@@ -80,5 +82,5 @@ view model =
div []
[ input [ onInput Change ] []
, button [ onClick Check ] [ text "Check" ]
- , div [] [ text (String.join ", " model.suggestions) ]
+ , div [ class "results" ] [ text (String.join ", " model.suggestions) ]
]
And testing it like this:
describe "Spelling"
[ test "calls suggestions check port when some suggestion is send"
<| \() ->
spellingComponent
|> startForTest
|> find [ tag "input" ]
|> trigger "input" "{\"target\": {\"value\": \"cats\"}}"
|> find [ tag "button" ]
|> trigger "click" "{}"
|> assertCalled (Spelling.check "cats")
, test "renders received suggestions"
<| \() ->
spellingComponent
|> startForTest
|> update (Spelling.Suggest [ "dogs", "cats" ])
|> find [ class "results" ]
|> assertText (Expect.equal "dogs, cats")
]
Here are complete tests for the Spelling example.
There is also an example for testing WebSockets.
To convert your testable init
, update
and view
functions into functions that work with Html.program
, use the Testable
module:
main : Program Never Model Msg
main =
Html.program
{ init = Testable.init MyComponent.init
, update = Testable.update MyComponent.update
, view = Testable.view MyComponent.view
, subscriptions = MyComponent.subscriptions
}
There are still some pending functionalities, like spawning tasks, http.progress, effect managers and so on.
The Http API is not fully compatible with the original one, as you cannot pass Expect, only the Decoder for the request function, but it shouldn't be a problem unless you are doing some advanced stuff.
Also, the Html Selectors probably have some missing cases, like select a child in a specific position. If you need some Selector that is not there yet, please open an issue or send a PR.