A ProgramTest simulates the execution of an Elm program
enabling you write high-level tests for your program.
(Testing your programs at this level
provides test coverage that is resilient even to drastic refactorings of your application architecture,
and encourages tests that make it clear how end-users and external services will interact with your program.)
This module allows you to interact with your program by simulating user interactions and external events (like HTTP responses and ports), and making assertions about the HTML it renders and the external requests it makes.
The list below is an index into the API documentation for the assertion and simulation functions relevant to each topic:
For a more detailed explanation of how to get started, see the elm-program-test guidebooks (the best one to start with is “Testing programs with interactive views”):
Http.get and Http.postStarts the given test program by initializing it with the given flags.
If your program uses Json.Encode.Value as its flags type,
you may find withJsonStringFlags useful.
A ProgramDefinition (required to create a ProgramTest with start)
can be created with one of the following functions that parallel
the functions in elm/browser for creating programs.
Creates a ProgramDefinition from the parts of a Browser.sandbox program.
See other create* functions below if the program you want to test does not use Browser.sandbox.
Creates a ProgramTest from the parts of a Browser.element program.
See other create* functions below if the program you want to test does not use Browser.element.
If your program has subscriptions that you want to simulate, see withSimulatedSubscriptions.
Creates a ProgramTest from the parts of a Browser.document program.
See other create* functions if the program you want to test does not use Browser.document.
If your program has subscriptions that you want to simulate, see withSimulatedSubscriptions.
Creates a ProgramTest from the parts of a Browser.application program.
See other create* functions if the program you want to test does not use Browser.application.
If your program has subscriptions that you want to simulate, see withSimulatedSubscriptions.
Note that Elm currently does not provide any way to create a Browser.Navigation.Key in tests, so this function uses () as the key type instead.
For an example of how to test such a program, see
NavigationKeyExample.elm
and NavigationKeyExampleTest.elm.
Creates a ProgramTest from the parts of a Platform.worker program.
See other create* functions if the program you want to test does not use Platform.worker.
If your program has subscriptions that you want to simulate, see withSimulatedSubscriptions.
Represents an unstarted program test.
Use start to start the program being tested.
The following functions allow you to configure your
ProgramDefinition before starting it with start.
Sets the initial browser URL
You must set this when using createApplication,
or when using clickLink and expectPageChange
to simulate a user clicking a link with relative URL.
Provides a convenient way to provide flags for a program that decodes flags from JSON.
By providing the JSON decoder, you can then provide the flags as a JSON string when calling
start.
This allows you to provide a function that lets ProgramTest simulate effects that would become Cmds and Tasks
when your app runs in production
(this enables you to use simulateHttpResponse, advanceTime, etc.).
For a detailed explanation and example of how to set up tests that use simulated effects,
see the “Testing programs with Cmds” guidebook.
You only need to use this if you need to simulate HTTP requests, outgoing ports, or the passing of time.
See the SimulatedEffect.* modules in this package for functions that you can use to implement
the required effect -> SimulatedEffect msg function for your effect type.
This represents an effect that elm-program-test is able to simulate.
When using withSimulatedEffects you will provide a function that can translate
your program's effects into SimulatedEffects.
(If you do not use withSimulatedEffects,
then ProgramTest will not simulate any effects for you.)
You can create SimulatedEffects using the following modules,
which parallel the modules your real program would use to create Cmds and Tasks:
SimulatedEffect.Http (parallels Http from elm/http)SimulatedEffect.Cmd (parallels Platform.Cmd from elm/core)SimulatedEffect.Navigation (parallels Browser.Navigation from elm/browser)SimulatedEffect.Ports (parallels the port keyword)SimulatedEffect.Task (parallels Task from elm/core)SimulatedEffect.Process (parallels Process from elm/core)SimulatedEffect.Time (parallels Time from elm/time)Similar to SimulatedEffect, but represents a Task instead of a Cmd.
This allows you to provide a function that lets ProgramTest simulate subscriptions that would be Subs
when your app runs in production
(this enables you to use simulateIncomingPort, etc.).
You only need to use this if you need to simulate subscriptions in your test.
For a detailed explanation and example of how to set up tests that use simulated subscriptions,
see the “Testing programs with ports” guidebook.
The function you provide should be similar to your program's subscriptions function
but return SimulatedSubs instead of Subs.
See the SimulatedEffect.* modules in this package for functions that you can use to implement
the required model -> SimulatedSub msg function.
This represents a subscription that elm-program-test is able to simulate.
When using withSimulatedSubscriptions you will provide
a function that is similar to your program's subscriptions function but that
returns SimulatedSubs instead Subs.
(If you do not use withSimulatedSubscriptions,
then ProgramTest will not simulate any subscriptions for you.)
You can create SimulatedSubs using the following modules:
SimulatedEffect.Ports (parallels the port keyword)Ends a ProgramTest, reporting any errors that occurred.
You can also end a ProgramTest using any of the functions starting with expect*.
In fact, you should prefer using one of the expect* functions when possible,
as doing so will make the intent of your test more clear.
A simpler way to assert that a ProgramTest's view matches a given selector.
expectViewHas [...selector...] is the same as expectView (Test.Html.Query.has [...selector...]).
If you want to interact with the program more after this assertion, see ensureViewHas.
A simpler way to assert that a ProgramTest's view does not match a given selector.
expectViewHasNot [...selector...] is the same as expectView (Test.Html.Query.hasNot [...selector...]).
If you want to interact with the program more after this assertion, see ensureViewHasNot.
Makes an assertion about the current state of a ProgramTest's view.
If you want to interact with the program more after this assertion, see ensureView.
See the documentation for expectViewHas.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectViewHas when possible,
as having a single assertion per test can make the intent of your tests more clear.
See the documentation for expectViewHasNot.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectViewHasNot when possible,
as having a single assertion per test can make the intent of your tests more clear.
See the documentation for expectView.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectView when possible,
as having a single assertion per test can make the intent of your tests more clear.
Simulates clicking a button.
This function will find and click a <button> HTML node containing the given buttonText.
It will also try to find and click elements with the accessibility label role="button".
If the button is disabled the test will fail.
Simulates clicking a <a href="..."> link.
The parameters are:
The text of the <a> tag (which is the link text visible to the user).
The href of the <a> tag.
NOTE: After eeue56/elm-html-test#52 is resolved,
a future release of this package will remove the href parameter.
Note for testing single-page apps:
if the target <a> tag has an onClick handler,
then the message produced by the handler will be processed
and the href will not be followed.
NOTE: Currently this function cannot verify that the onClick handler
sets preventDefault, but this will be done in the future after
https://github.com/eeue56/elm-html-test/issues/63 is resolved.
Simulates replacing the text in an input field labeled with the given label.
The id of the input field
(which must match both the id attribute of the target input element,
and the for attribute of the label element),
or "" if the <input /> is a descendant of the <label>.
NOTE: After eeue56/elm-html-test#52 is resolved, a future release of this package will remove this parameter.
The label text of the input field.
The text that will be entered into the input field.
There are a few different ways to accessibly label your input fields so that fillIn will find them:
You can place the <input /> element inside a <label> element that also contains the label text.
<label>
Favorite fruit
<input>
</label>You can place the <input /> and a <label> element anywhere on the page and link them with a unique id.
<label for="fruit">Favorite fruit</label>
<input id="fruit"></input>You can use the aria-label attribute.
<input aria-label="Favorite fruit"></input>If you need to target a <textarea> that does not have a label,
see fillInTextarea.
If you need more control over finding the target element or creating the simulated event,
see simulateDomEvent.
Simulates replacing the text in a <textarea>.
This function expects that there is only one <textarea> in the view.
If your view has more than one <textarea>,
prefer adding associated <label> elements and use fillIn.
If you cannot add <label> elements see within.
If you need more control over finding the target element or creating the simulated event,
see simulateDomEvent.
Simulates setting the value of a checkbox labeled with the given label.
The parameters are:
The id of the input field
(which must match both the id attribute of the target input element,
and the for attribute of the label element),
or "" if the <input /> is a descendant of the <label>.
NOTE: After eeue56/elm-html-test#52 is resolved, a future release of this package will remove this parameter.
The label text of the input field
A Bool indicating whether to check (True) or uncheck (False) the checkbox.
NOTE: In the future, this will be generalized to work with aria accessibility attributes in addition to working with standard HTML label elements.
If you need more control over finding the target element or creating the simulated event,
see simulateDomEvent.
Simulates choosing an option with the given text in a select with a given label
The parameters are:
The id of the <select>
(which must match both the id attribute of the target select element,
and the for attribute of the label element),
or "" if the <select> is a descendant of the <label>.
NOTE: After eeue56/elm-html-test#52 is resolved, a future release of this package will remove this parameter.
The label text of the select.
The value of the <option> that will be chosen.
NOTE: After eeue56/elm-html-test#51 is resolved, a future release of this package will remove this parameter.
The user-visible text of the <option> that will be chosen.
Example: If you have a view like the following,
import Html
import Html.Attributes exposing (for, id, value)
import Html.Events exposing (on, targetValue)
Html.div []
[ Html.label [ for "pet-select" ] [ Html.text "Choose a pet" ]
, Html.select
[ id "pet-select", on "change" targetValue ]
[ Html.option [ value "dog" ] [ Html.text "Dog" ]
, Html.option [ value "hamster" ] [ Html.text "Hamster" ]
]
]you can simulate selecting an option like this:
ProgramTest.selectOption "pet-select" "Choose a pet" "dog" "Dog"If you need more control over finding the target element or creating the simulated event,
see simulateDomEvent.
Simulates a custom DOM event.
NOTE: If there is another, more specific function (see “Simulating user input”) that does what you want, prefer that instead, as you will get the benefit of better error messages.
The parameters are:
A function to find the HTML element that responds to the event
(typically this will be a call to Test.Html.Query.find [ ...some selector... ])
The event to simulate (see Test.Html.Event "Event Builders")
Focus on a part of the view for a particular operation.
For example, if your view produces the following HTML:
<div>
<div id="sidebar">
<button>Submit</button>
</div>
<div id="content">
<button>Submit</button>
</div>
</div>then the following will allow you to simulate clicking the "Submit" button in the sidebar
(simply using clickButton "Submit" would fail because there are two buttons matching that text):
import Test.Html.Query as Query
import Test.Html.Selector exposing (id)
programTest
|> ProgramTest.within
(Query.find [ id "sidebar" ])
(ProgramTest.clickButton "Submit")
|> ...Asserts that an HTTP request to the specific url and method has been made.
The parameters are:
"GET" or "POST")For example:
...
|> expectHttpRequestWasMade "GET" "https://example.com/api/data"If you want to check the headers or request body, see expectHttpRequest.
If you expect multiple requests to have been made to the same endpoint, see expectHttpRequests.
NOTE: You must use withSimulatedEffects before you call start to be able to use this function.
If you want to interact with the program more after this assertion, see ensureHttpRequestWasMade.
Allows you to check the details of a pending HTTP request.
See the “Expectations” section of Test.Http for functions that might be helpful
in create an expectation on the request.
If you only care about whether the a request was made to the correct URL, see expectHttpRequestWasMade.
...
|> expectHttpRequest "POST"
"https://example.com/save"
(.body >> Expect.equal """{"content":"updated!"}""")If you expect multiple requests to have been made to the same endpoint, see expectHttpRequests.
NOTE: You must use withSimulatedEffects before you call start to be able to use this function.
If you want to interact with the program more after this assertion, see ensureHttpRequest.
Allows you to check the details of pending HTTP requests.
See the “Expectations” section of Test.Http for functions that might be helpful
in create an expectation on the request.
If your program will only have a single pending request to any particular URL, you can use the simpler expectHttpRequest (singular) or expectHttpRequestWasMade instead.
...
|> expectHttpRequests "POST"
"https://example.com/save"
(List.map .body >> Expect.equal ["""body1""", """body2"""])NOTE: You must use withSimulatedEffects before you call start to be able to use this function.
If you want to interact with the program more after this assertion, see ensureHttpRequests.
See the documentation for expectHttpRequestWasMade.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectHttpRequestWasMade when possible,
as having a single assertion per test can make the intent of your tests more clear.
See the documentation for expectHttpRequest.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectHttpRequest when possible,
as having a single assertion per test can make the intent of your tests more clear.
See the documentation for expectHttpRequests.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectHttpRequests when possible,
as having a single assertion per test can make the intent of your tests more clear.
Simulates an HTTP 200 response to a pending request with the given method and url.
The parameters are:
"GET" or "POST")For example:
...
|> simulateHttpOk "GET"
"https://example.com/time.json"
"""{"currentTime":1559013158}"""
|> ...If you need to simulate an error, a response with a different status code,
or a response with response headers,
see simulateHttpResponse.
If you want to check the request headers or request body, use ensureHttpRequest
immediately before using simulateHttpOk.
NOTE: You must use withSimulatedEffects before you call start to be able to use this function.
Simulates a response to a pending HTTP request. The test will fail if there is no pending request matching the given method and url.
The parameters are:
"GET" or "POST")Http.Response value for the simulated response. You may find it helpful to see the “Responses” section in Test.Http
for convenient ways to create Http.Response values.For example:
...
|> simulateHttpResponse "GET"
"https://example.com/time.json"
Test.Http.networkError
|> ...
|> simulateHttpResponse "POST"
"https://example.com/api/v1/process_data"
(Test.Http.httpResponse
{ statusCode : 204
, headers : [ ( "X-Procesing-Time", "1506ms") ]
, body : ""
}
)If you are simulating a 200 OK response and don't need to provide response headers,
you can use the simpler simulateHttpOk.
If you want to check the request headers or request body, use ensureHttpRequest
immediately before using simulateHttpResponse.
If your program will make multiple pending requests to the same URL, see simulateHttpResponseAdvanced.
NOTE: You must use withSimulatedEffects before you call start to be able to use this function.
Simulates a response to one of several pending HTTP requests made to a given endpoint.
This is the same as simulateHttpResponse,
except that the additional Int parameter specificies which request to resolve if multiple requests to the same method/URL are pending.
Simulates the passing of time.
The Int parameter is the number of milliseconds to simulate.
This will cause any pending Task.sleeps to trigger if their delay has elapsed.
NOTE: You must use withSimulatedEffects before you call start to be able to use this function.
Lets you assert on the values that the program being tested has sent to an outgoing port.
The parameters are:
ensureOutgoingPortValues)
and return an ExpectationFor example:
...
|> expectOutgoingPortValues
"saveApiTokenToLocalStorage"
Json.Decode.string
(Expect.equal [ "975774a26612", "920facb1bac0" ])For a more detailed explanation and example, see the “Testing programs with ports” guidebook.
NOTE: You must use withSimulatedEffects before you call start to be able to use this function.
If you want to interact with the program more after this assertion, see ensureOutgoingPortValues.
See the documentation for expectOutgoingPortValues.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectOutgoingPortValues when possible,
as having a single assertion per test can make the intent of your tests more clear.
Lets you simulate a value being sent to the program being tested via an incoming port.
The parameters are:
For example, here we are simulating the program receiving a list of strings on the incoming port
port resultsFromJavascript : (List String -> msg) -> Sub msg:
...
|> ProgramTest.simulateIncomingPort
"resultsFromJavascript"
(Json.Encode.list Json.Encode.string
[ "Garden-path sentences can confuse the reader." ]
)For a more detailed explanation and example, see the “Testing programs with ports” guidebook.
NOTE: You must use withSimulatedSubscriptions before you call start to be able to use this function.
Asserts that the program ended by navigating away to another URL.
The parameter is:
If your program is an application that manages URL changes
(created with createApplication),
then you probably want expectBrowserUrl instead.
Asserts on the current value of the browser URL bar in the simulated test environment.
The parameter is:
Expect.equal with the exact URL you expect.If your program is not an application that manages URL changes
and you want to assert that the user clicked a link that goes to an external web page,
then you probably want expectPageChange instead.
Asserts on the current browser history in the simulated test environment.
This only makes sense if you are using withSimulatedEffects
and the function you provide to it produces
SimulatedEffect.Navigation.replaceUrl or
SimulatedEffect.Navigation.pushUrl
for one or more of your effects.
The previous URL is added to the simulated browser history whenever a pushUrl effect is simulated.
The parameter is:
Example: If there's only one expected item in the history or if you want check the complete history since the start of the test, use this with Expect.equal
createApplication { ... }
|> withBaseUrl "https://example.com/resource/123"
|> start ()
|> clickButton "Details"
|> expectBrowserHistory (Expect.equal [ "https://example.com/resource/123/details" ])Example: If there might be multiple items in the history and you only want to check the most recent item:
createApplication { ... }
|> withBaseUrl "https://example.com/resource/123"
|> start ()
|> clickButton "Details"
|> clickButton "Calendar"
|> expectBrowserHistory (List.head >> Expect.equal (Just "https://example.com/resource/123/calendar"))If you need to assert on the current URL, see expectBrowserUrl.
See the documentation for expectBrowserUrl.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectBrowserUrl when possible,
as having a single assertion per test can make the intent of your tests more clear.
See the documentation for expectBrowserHistory.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectBrowserHistory when possible,
as having a single assertion per test can make the intent of your tests more clear.
Simulates a route change event (which would happen when your program is
a Browser.application and the user manually changes the URL in the browser's URL bar).
The parameter may be an absolute URL or relative URL.
You should avoid the functions below when possible, but you may find them useful to test things that are not yet directly supported by elm-program-test.
Advances the state of the ProgramTest by applying the given msg to your program's update function
(provided when you created the ProgramTest).
This can be used to simulate events that can only be triggered by commands (Cmd) and subscriptions (Sub)
(i.e., that cannot be triggered by user interaction with the view).
NOTE: When possible, you should prefer Simulating user input,
Simulating HTTP responses,
or (if neither of those support what you need) simulateLastEffect,
as doing so will make your tests more resilient to changes in your program's implementation details.
Make an assertion about the current state of a ProgramTest's model.
When possible, you should prefer making assertions about the rendered view (see expectView)
or external requests made by your program (see expectHttpRequest, expectOutgoingPortValues),
as testing at the level that users and external services interact with your program
will make your tests more resilient to changes in the private implementation of your program.
Makes an assertion about the last effect produced by a ProgramTest's program.
NOTE: If you are asserting about HTTP requests or outgoing ports, you should prefer more specific functions designed for that purpose. You can find links to the relevant documentation in the documentation index.
If you want to interact with the program more after this assertion, see ensureLastEffect.
See the documentation for expectLastEffect.
This is the same except that it returns a ProgramTest instead of an Expectation
so that you can interact with the program further after this assertion.
You should prefer expectLastEffect when possible,
as having a single assertion per test can make the intent of your tests more clear.
Simulate the outcome of the last effect produced by the program being tested
by providing a function that can convert the last effect into msgs.
The function you provide will be called with the effect that was returned by the most recent call to update or init in the ProgramTest.
Err, then the ProgramTest will enter a failure state with the provided error message.Ok, then the list of msgs will be applied in order via ProgramTest.update.NOTE: If you are simulating HTTP responses, you should prefer more specific functions designed for that purpose. You can find links to the relevant documentation in the documentation index.
These functions may be useful if you are writing your own custom assertion functions.
fail can be used to report custom errors if you are writing your own convenience functions to deal with program tests.
For example, this function checks for a particular structure in the program's view,
but will also fail the ProgramTest if the expectedCount parameter is invalid:
expectNotificationCount : Int -> ProgramTest model msg effect -> Expectation
expectNotificationCount expectedCount programTest =
if expectedCount <= 0 then
programTest
|> ProgramTest.fail "expectNotificationCount"
("expectedCount must be positive, but was: " ++ String.fromInt expectedCount)
else
programTest
|> expectViewHas
[ Test.Html.Selector.class "notifications"
, Test.Html.Selector.text (toString expectedCount)
]If you are writing a convenience function that is creating a program test, see createFailed.
createFailed can be used to report custom errors if you are writing your own convenience functions to create program tests.
NOTE: if you are writing a convenience function that takes a ProgramTest as input, you should use fail instead,
as it provides more context in the test failure message.
The parameters are:
For example:
-- JsonSchema and MyProgram are imaginary modules for this example
import JsonSchema exposing (Schema, validateJsonSchema)
import MyProgram exposing (Model, Msg)
import ProgramTest exposing (ProgramTest)
createWithValidatedJson : Schema -> String -> ProgramTest Model Msg (Cmd Msg)
createWithValidatedJson schema json =
case validateJsonSchema schema json of
Err message ->
ProgramTest.createFailed
"createWithValidatedJson"
("JSON schema validation failed:\n" ++ message)
Ok () ->
ProgramTest.createElement
{ init = MyProgram.init
, update = MyProgram.update
, view = MyProgram.view
}
|> ProgramTest.start jsonThis can be used for advanced helper functions where you want to continue a test but need the data sent out through ports later in your test.
NOTE: If you do not need this advanced functionality,
prefer expectOutgoingPortValues instead.
A
ProgramTestrepresents an Elm program, a current state for that program, information about external effects that have been produced by the program (such as pending HTTP requests, values sent to outgoing ports, etc), and a log of any errors that have occurred while simulating interaction with the program.ProgramTest, see thecreate*functions below.ProgramTest, see Simulating user input, or the many simulate functions in this module.ProgramTest, see the manyexpect*functions in this module.