Intro
In this post, I attempt to demonstrate how cool I am at writing proven code for a user’s registration form. Please note that I use the term proven and testable interchangeably.
The viewmodel is the root of the testable codebase for business logic.
As a result, I injected the function (aka: implementation) that handles the submit request for a registration form inside my viewmodel.
Here’s code that shows a workflow accepting the implementation of a submit request inside my viewmodel:
type ViewModel(implementation:Try.SubmitFn) as x = ... let submit() = let publishEvents events = events |> List.iter(fun event -> eventOccurred.Trigger(event)) validatedForm |> function | Some form -> form |> Command.Execute |> In.SubmitRegistration.workflow implementation |> publishEvents | None -> ()
Here’s the entire viewmodel:
namespace Nikeza.Mobile.UILogic.Registration open System.Windows.Input open Nikeza.Mobile.UILogic open Nikeza.Mobile.Profile.Commands.Registration open Nikeza.Mobile.Profile.Events open Adapter open In type Form = Registration.Types.Form module Updates = let statusOf formValidated events = events |> List.exists formValidated type ViewModel(implementation:Try.SubmitFn) as x = let mutable validatedForm = None let eventOccurred = new Event<_>() let validate() = let isValidated = function | FormValidated form -> validatedForm <- Some form; true | _ -> false { Form.Email= x.Email Form.Password= x.Password Form.Confirm= x.Confirm } |> ofUnvalidated |> Validate.Execute |> In.ValidateRegistration.workflow |> Updates.statusOf isValidated let submit() = let publishEvents events = events |> List.iter(fun event -> eventOccurred.Trigger(event)) validatedForm |> function | Some form -> form |> Command.Execute |> In.SubmitRegistration.workflow implementation |> publishEvents | None -> () let validateCommand = DelegateCommand( (fun _ -> x.IsValidated <- validate()) , fun _ -> true) let submitCommand = DelegateCommand( (fun _ -> submit() |> ignore), fun _ -> x.IsValidated <- validate(); x.IsValidated ) let mutable email = "" let mutable password = "" let mutable confirm = "" let mutable isValidated = false member x.Validate = validateCommand :> ICommand member x.Submit = submitCommand :> ICommand [<CLIEvent>] member x.EventOccured = eventOccurred.Publish member x.Email with get() = email and set(value) = email <- value member x.Password with get() = password and set(value) = password <- value member x.Confirm with get() = confirm and set(value) = confirm <- value member x.IsValidated with get() = isValidated and set(value) = isValidated <- value
Test
An automated test targeting business logic should provide its own implementation for submitting a registration request.
My test for submitting a registration form is as follows:
[<Test>] let ``Registration submitted after being validated`` () = // Setup let mutable registrationSucceeded = false let registration = ViewModel(mockSubmit) registration.FillIn() .EventOccured .Add(fun event -> event |> function | RegistrationSucceeded _ -> registrationSucceeded <- true | RegistrationFailed _ -> registrationSucceeded <- false) registration.Validate.Execute() // Test registration.Submit.Execute() // Verify registrationSucceeded |> should equal true
Test API
As I stated earlier, I try to be cool when I write code. Thus, to validate my coolness, I used a type extension to initialize the registration viewmodel.
Here’s my test API:
module TestAPI open Nikeza.Mobile.UILogic.Registration open Try let someEmail = "scott@abc.com" let somePassword = "some_password" let mockSubmit : SubmitFn = fun _ -> Ok { Id = "" FirstName = "" LastName = "" Email = "" Bio = "" ImageUrl = "" Sources = [] } type ViewModel with member x.FillIn () = x.Email <- someEmail x.Password <- somePassword x.Confirm <- somePassword x
Usage
The workflow below relies on a SubmitFn which was originally injected into my viewmodel.
The workflow that receives the implementation of this dependency is as follows:
module SubmitRegistration = type private SubmitWorkflow = SubmitFn -> Command -> RegistrationSubmissionEvent list let workflow : SubmitWorkflow = fun submitFn command -> command |> function Command.Execute form -> form |> submitFn |> ResultOf.Submit.Executed |> Are.Registration.Submission.events