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.