Testing
The Model-View-Update architecture used by Fabulous makes it simple to unit test every part of your application.
Apps are composed of 3 key pure F# functions: init, update and view
They take some parameters and return a value. Ideal for unit testing.
Testing init
initinit is the easiest one to test.
It usually takes nothing and returns a value.
Let’s take this code for example:
type Model =
{ Count: int
Step: int }
let init () =
{ Count = 0; Step = 1 }Here we can make sure that the default state stays exact throughout the life of the project.
So using our favorite unit test framework (here we use FsUnit for this example), we can write a test that will check if the value returned by init is the one we expect.
[<Test>]
let ``Init should return a valid initial state``() =
App.init () |> should equal { Count = 0; Step = 1 }Testing update
updateupdate can be more complex but it remains a pure F# function.
Testing it is equivalent to what we just did with init.
Let’s take this code for example:
We can write the following tests:
Testing init and update when using commands
init and update when using commandsCommands are a great way for executing a set of tasks (asynchronous or not) after receiving a message.
But behind the scenes, Cmd<'msg> is really only an array of functions. This makes testing Cmd<'msg> really difficult (no way to know what the functions are) and the functions init and update as well.
In the case you want to unit test your code, even if you’re using Cmd<'msg> inside initand update, the best way is to use of the CmdMsg pattern.
This is a general pattern, applicable when using an Elm-like programming model. It is not linked to Fabulous specifically.
Fabulous only provides some helpers to help you achieve this with less code.
The principle is to replace any direct usage of Cmd<'msg> from init and update, and instead use a discriminated union called CmdMsg.
Doing this transforms the output of both init and update to pure data output, which can then be easily unit tested
The actual commands are still executed as Cmd<'msg> though.
So in order to make this work with Fabulous, you need a function that will convert a CmdMsg to a Cmd<'msg>
Fabulous then helps you boot your application using Program.statefulWithCmdMsg
Note that Program.statefulWithCmdMsg doesn’t do anything magic.
It only applies mapCommands to any CmdMsg returned by init and update.
You could achieve the exact same behavior by converting them yourself and using Program.stateful.
Last updated