前回の続き。 書籍では、依存性の注入は関数の部分適用によってなされるためドメインのコア部分はテストが容易、とのことなので実際そうなのかを確かめてみる。
単純ではあるが長くなったので適当に省略しつつ placeOrder
ワークフローのエントリポイントの一部を抜粋する。
open FsToolkit.ErrorHandling open Expecto open Expecto.Flip module PlaceOrder = open OrderTaking.Common open OrderTaking.PlaceOrder open OrderTaking.PlaceOrder.Implementation let placeOrder = let checkProductExists _ = true let checkAddressExists address = asyncResult { return CheckedAddress address } let getProductPrice _ = Price.unsafeCreate 10M let createOrderAcknowledgmentLetter _ = HtmlString "" let sendOrderAcknowledgment _ = Sent let unvalidatedOrder = { UnvalidatedOrder.OrderId = "1" CustomerInfo = { FirstName = "foo" LastName = "bar" EmailAddress = "foobar@example.com" } ShippingAddress = { AddressLine1 = "hoge" AddressLine2 = "" AddressLine3 = "" AddressLine4 = "" City = "fuga" ZipCode = "12345" } BillingAddress = { AddressLine1 = "piyo" AddressLine2 = "" AddressLine3 = "" AddressLine4 = "" City = "poyo" ZipCode = "67890" } Lines = [ { OrderLineId = "1" ProductCode = "W9999" Quantity = 2M } { OrderLineId = "2" ProductCode = "G999" Quantity = 3M } ] } testList "placeOrder" [ testAsync "should return events AcknowledgmentSent, OrderPlaced and BillableOrderPlaced with valid order" { let! result = placeOrder checkProductExists checkAddressExists getProductPrice createOrderAcknowledgmentLetter sendOrderAcknowledgment unvalidatedOrder result |> Expect.wantOk "should be OK" |> Expect.equal "should equal" [ AcknowledgmentSent { OrderId = OrderId.create "" "1" |> Expect.wantOk "" EmailAddress = EmailAddress.create "" "foobar@example.com" |> Expect.wantOk "" } OrderPlaced { OrderId = OrderId.create "" "1" |> Expect.wantOk "" CustomerInfo = { Name = { FirstName = String50.create "" "foo" |> Expect.wantOk "" LastName = String50.create "" "bar" |> Expect.wantOk "" } EmailAddress = EmailAddress.create "" "foobar@example.com" |> Expect.wantOk "" } ShippingAddress = { AddressLine1 = String50.create "" "hoge" |> Expect.wantOk "" AddressLine2 = None AddressLine3 = None AddressLine4 = None City = String50.create "" "fuga" |> Expect.wantOk "" ZipCode = ZipCode.create "" "12345" |> Expect.wantOk "" } BillingAddress = { AddressLine1 = String50.create "" "piyo" |> Expect.wantOk "" AddressLine2 = None AddressLine3 = None AddressLine4 = None City = String50.create "" "poyo" |> Expect.wantOk "" ZipCode = ZipCode.create "" "67890" |> Expect.wantOk "" } AmountToBill = BillingAmount.create 50M |> Expect.wantOk "" Lines = [ { OrderLineId = OrderLineId.create "" "1" |> Expect.wantOk "" ProductCode = WidgetCode.create "" "W9999" |> Expect.wantOk "" |> Widget Quantity = UnitQuantity.create "" 2 |> Expect.wantOk "" |> Unit LinePrice = Price.unsafeCreate 20M } { OrderLineId = OrderLineId.create "" "2" |> Expect.wantOk "" ProductCode = GizmoCode.create "" "G999" |> Expect.wantOk "" |> Gizmo Quantity = KilogramQuantity.create "" 3M |> Expect.wantOk "" |> Kilogram LinePrice = Price.unsafeCreate 30M } ] } BillableOrderPlaced { OrderId = OrderId.create "" "1" |> Expect.wantOk "" BillingAddress = { AddressLine1 = String50.create "" "piyo" |> Expect.wantOk "" AddressLine2 = None AddressLine3 = None AddressLine4 = None City = String50.create "" "poyo" |> Expect.wantOk "" ZipCode = ZipCode.create "" "67890" |> Expect.wantOk "" } AmountToBill = BillingAmount.create 50M |> Expect.wantOk "" } ] } testAsync "should return ValidationError when product not found" { let checkProductExists _ = false let! result = placeOrder checkProductExists checkAddressExists getProductPrice createOrderAcknowledgmentLetter sendOrderAcknowledgment unvalidatedOrder result |> Expect.wantError "should be Error" |> Expect.equal "should equal" (Validation(ValidationError "Invalid: Widget WidgetCode \"W9999\"")) } ] let tests = testList "PlaceOrder" [ placeOrder ] [<EntryPoint>] let main args = runTestsWithCLIArgs [] args (testList "OrderTaking" [ PlaceOrder.tests ])
まぁ普通にそうなるよねって感じだった。
Expecto
固有の話だが、Expect.wantOk
がそこそこ煩く感じる。
とはいえドメイン型の正当性を保証するためなら仕方ないか。
テストでなら Price.unsafeCreate
のような裏道を意識して使うのは全然アリだろう。