I'm attempting to test a debouncing function in my Elm application and can't figure out how.
The debouncing is applied to a text field for fuzzy search to avoid making too many http requests, it is modelled on this example https://ellie-app.com/jNmstCdv3va1 and follows the same logic.
type alias Model =
{ search : Maybe String
, searchResult : List User
, debouncingCounter : Int
}
init : Model
init =
{ search = Nothing
, searchResult = []
, debouncingCounter = 0
}
debounceTime : Time
debounceTime = 350 * Time.millisecond
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
(...)
SearchInput search ->
let
newCounter = model.debouncingCounter + 1
in
case search o
"" -> ({model | search = Nothing, searchResult = []}, Cmd.none)
_ ->
({ model | search = Just search, debouncingCounter = newCounter }
, Process.sleep debounceTime |> Task.perform (always (Timeout newCounter)))
Timeout int ->
if int==model.debouncingCounter then
(update SendSearch {model | debouncingCounter = 0 })
else
(update NoOperation model)
SendSearch ->
case model.search of
Nothing ->
(model, Cmd.none)
Just string ->
let
cmd = Http.send ReSendSearch <| postApiAdminUserSearchByQuery string
in
(model, cmd)
ReSendSearch result ->
case result of
Err _ ->
(model, Cmd.none)
Ok usersList ->
({model | searchResult = usersList}, Cmd.none )
I want to ensure that, after calling
update (searchInput "string") init
the Http request is only sent after the debounceTime.
I can easily test the model right after the update function is called with the searchInput message. For instance, here I check that the initial value of the "debouncingCounter" field in the model gets set to 1:
startDebounce : Test
startDebounce =
test "debouncingCounter is set to 1 after search input is updated" <|
\_ ->
Users.init
|> Users.update (Users.SearchInput "abc")
|> Tuple.first
|> .debouncingCounter
|> Expect.equal 1
However, I don't see how I would be able to test the effects of the delayed Cmd Msg on the model since I can't directly apply the cmd value returned by the update function.
Process.sleep debounceTime |> Task.perform (always (Timeout newCounter))
It seems that different ways of implementing debouncing won't solve the problem as they all rely on command messages.
Depending on what exactly you want to test, you might follow different approaches.
If you want to test
Your code on SearchInput
returning the proper command: you may consider using elm-testable
.
The elm runtime executing the Process.sleep
command properly: this will fall into the integration/end-to-end test scenarios. So, you will need to test the complete compiled app, using one of the end-to-end/JS tools.
Your code processing the Timeout x
messages properly: just write a separate test case for that.