How to test user interactions in Elm

Testing user interactions in Elm involves creating tests to ensure that our Elm application behaves as expected. This can include actions like clicking buttons, entering data into forms, and navigating between pages. The prime purpose of testing user interactions is to detect or prevent bugs or unexpected behavior that might arise from these interactions. Apart from this, some key aspects involved in testing user interactions involve managing user expectations, making the application more responsive, and ensuring that the application’s state managementIn the context of a software application, including web applications built with Elm, state management refers to the process of managing and maintaining the current state or data of the application. is accurate.

Step-by-step method

To get started with testing user interactions in Elm, we will need to set up some testing tools and write test cases. The way to achieve this objective is outlined in the steps below.

Step 1: Creating an Elm app

To start off, we navigate to the directory in which we want to create our Elm application and execute the elm init command. This creates a /src folder for adding any Elm files we wish to add.

Note: Before executing the elm init command, we must ensure to have the elm-test and elm frameworks installed. To do this, we can execute the command: npm install -g elm elm-testThe -g flag makes the installation global.

Step 2: Defining, updating, and viewing functions

Firstly, we create the update function that takes a custom message type called Msg and an integer model as arguments. This function uses pattern matching on the message, where if the function receives an Increment message, it increments the model by one and returns the new model.

--custom type msg declared with only one message value called "Increment"
type Msg
= Increment
--update function named as updateWithEvent
updateWithEvent : Msg -> Int -> Int
updateWithEvent msg model =
case msg of
Increment ->
model + 1
Creating the update function

Next, we create the view function that takes an integer model as an argument and returns an HTML view that consists of a Html.div element with a class container being set to "container".

--view function with the name viewWithModel
viewWithModel : Int -> Html Msg
viewWithModel model =
Html.div [ class "container" ]
[ Html.h1 [] [ Html.text "Hello, Elm!" ]
, Html.button [ class "btn", onClick Increment ] [ Html.text "Click Me" ]
]
The view function being created

In the function above, we create an Html.h1 element with the text "Hello, Elm!", an Html.button element set to "btn"A CSS class name used as the attribute for the class attribute of the element. , and the text "Click Me" present inside the Html.div element. Also, we attach an onClick attribute to the button, which sends an Increment message when clicked.

Finally, we initialize the main function, setting up the Elm application using Browser.sandbox. It specifies the update function (updateWithEvent), and the view function (viewWithModel) to create the application. There is no model to be initialized here, so the init argument is set to 0.

--main function named as mainWithEvent
mainWithEvent =
Browser.sandbox { init = 0, update = updateWithEvent, view = viewWithModel }
Main function being initialized

Step 3: Creating a test case

Then comes the main step, in which we create a test case for testing user interactions with the test suite below:

--tests value initialized to describe our tests
tests : Test
tests =
describe "Tests"
[ describe "User Interaction Tests"
[ test "Simulate button click" <|
\() ->
let
initialModel = 0
expectedModel = 1
in
--Expect.equal checks if the expected model matches the result of applying updateWithEvent to the initial model with the Increment message
Expect.equal expectedModel (updateWithEvent Increment initialModel)
]
]
Test suite for user interactions

Here, we have a top-level describe block named "Tests", within which we have another describe block named "User Interaction Tests". Inside the inner describe block, we define a test case using the test function.

The test simulates a button click and defines an initial and expected model. It uses Expect.equal to check if the expected model matches the result of applying the updateWithEvent function to the initial model when provided with the Increment message.

Step 4: Combining the code

Lastly, we merge all of the functions and test suite into one Elm file, naming it CounterTests.elm. The final code is shown below.

Note: Before performing this step, we must ensure all the necessary imports are present from Elm’s standard library to avoid any import errors.

module CounterTests exposing (..)
import Html.Attributes exposing (class)
import Browser
import Html exposing (Html, div, h1, button, text)
import Test exposing (Test, describe, test)
import Expect exposing (equal)
import Html.Events exposing (onClick)
type Msg
= Increment
updateWithEvent : Msg -> Int -> Int
updateWithEvent msg model =
case msg of
Increment ->
model + 1
viewWithModel : Int -> Html Msg
viewWithModel model =
Html.div [ class "container" ]
[ Html.h1 [] [ Html.text "Hello, Elm!" ]
, Html.button [ class "btn", onClick Increment ] [ Html.text "Click Me" ]
]
mainWithEvent =
Browser.sandbox { init = 0, update = updateWithEvent, view = viewWithModel }
tests : Test
tests =
describe "Tests"
[ describe "User Interaction Tests"
[ test "Simulate button click" <|
\() ->
let
initialModel = 0
expectedModel = 1
in
Expect.equal expectedModel (updateWithEvent Increment initialModel)
]
]
The CounterTests.elm file

After performing all the steps explained above, we execute the elm-test command to see the output of the test case executed in the Elm app.

Note: For the test cases to run successfully, we place the file into another folder named tests within the root directory of our Elm application, so that the elm-test command can access it.

Live code example

With this code example, we can see the output of the test cases being executed. Run it to see it in action!

module CounterTests exposing (..)

import Html.Attributes exposing (class)
import Browser
import Html exposing (Html, div, h1, button, text)
import Test exposing (Test, describe, test)
import Expect exposing (equal)
import Html.Events exposing (onClick)


type Msg
    = Increment

updateWithEvent : Msg -> Int -> Int
updateWithEvent msg model =
    case msg of
        Increment ->
            model + 1

viewWithModel : Int -> Html Msg
viewWithModel model =
    Html.div [ class "container" ]
        [ Html.h1 [] [ Html.text "Hello, Elm!" ]
        , Html.button [ class "btn", onClick Increment ] [ Html.text "Click Me" ]
        ]

mainWithEvent =
    Browser.sandbox { init = 0, update = updateWithEvent, view = viewWithModel }

tests : Test
tests =
    describe "Tests"
        
        [ describe "User Interaction Tests"
            [ test "Simulate button click" <|
                \() ->
                    let
                        initialModel = 0
                        expectedModel = 1
                    in
                    Expect.equal expectedModel (updateWithEvent Increment initialModel)
            ]
        ]
Testing user interactions in Elm

Conclusion

To conclude, testing user interactions in Elm is a key aspect in ensuring the correctness and reliability of our web applications. By using the Browser.sandbox module in combination with the Html.Events and Test modules, we can simulate user actions like button clicks and verify that the application responds as expected. Through a structured testing approach, we can gain confidence in the behavior of our Elm applications, resulting in more robust and maintainable code. This ultimately leads to a better user experience and reduced risk of bugs in production.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved