mirror of
https://github.com/Smaug123/WoofWare.Expect
synced 2025-10-08 22:08:38 +00:00
Comments and break the build to indicate missing functionality
This commit is contained in:
21
README.md
21
README.md
@@ -16,8 +16,7 @@ An [expect-testing](https://blog.janestreet.com/the-joy-of-expect-tests/) librar
|
|||||||
# Current status
|
# Current status
|
||||||
|
|
||||||
The basic mechanism works.
|
The basic mechanism works.
|
||||||
Snapshot updating is vibe-coded with Opus 4 and is purely text-based; I didn't want to use the F# compiler services because that's a pretty heavyweight dependency which should be confined to a separate test runner entity.
|
It's fairly well tested, but you will almost certainly be able to find ways to break it; try not to be too fancy with your syntax around the `snapshot` statement.
|
||||||
It's fairly well tested, but you will certainly be able to find ways to break it; try not to be too fancy with your syntax around the `snapshot` statement.
|
|
||||||
|
|
||||||
# How to use
|
# How to use
|
||||||
|
|
||||||
@@ -51,8 +50,17 @@ let ``With return! and snapshotThrows, you can see exceptions too`` () =
|
|||||||
snapshotThrows @"System.Exception: oh no"
|
snapshotThrows @"System.Exception: oh no"
|
||||||
return! (fun () -> failwith<int> "oh no")
|
return! (fun () -> failwith<int> "oh no")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``You can do lists more neatly with the snapshotList keyword`` () =
|
||||||
|
expect {
|
||||||
|
snapshotList [ "8" ; "9" ; "10" ; "11" ; "12" ]
|
||||||
|
return [ 8..12 ]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
(JSON output for elements is not yet supported with `snapshotList`.)
|
||||||
|
|
||||||
You can adjust the formatting:
|
You can adjust the formatting:
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
@@ -64,6 +72,15 @@ let ``Overriding the formatting`` () =
|
|||||||
snapshot @"Int32"
|
snapshot @"Int32"
|
||||||
return 123
|
return 123
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Overriding the formatting with lists`` () =
|
||||||
|
expect {
|
||||||
|
// these two lines *do* have to be in this order, for annoying reasons
|
||||||
|
snapshotList [ "8" ; "9" ; "0" ; "1" ; "2" ]
|
||||||
|
withFormat (fun x -> string<int> (x % 10))
|
||||||
|
return [ 8..12 ]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can override the JSON serialisation if you find the snapshot format displeasing:
|
You can override the JSON serialisation if you find the snapshot format displeasing:
|
||||||
|
@@ -5,18 +5,27 @@ open WoofWare.Expect
|
|||||||
|
|
||||||
[<TestFixture>]
|
[<TestFixture>]
|
||||||
module TestSnapshotList =
|
module TestSnapshotList =
|
||||||
|
[<OneTimeSetUp>]
|
||||||
|
let ``Prepare to bulk-update tests`` () =
|
||||||
|
// If you don't want to enter bulk-update mode, just replace this line with a no-op `()`.
|
||||||
|
// The `updateAllSnapshots` tear-down below will simply do nothing in that case.
|
||||||
|
GlobalBuilderConfig.enterBulkUpdateMode ()
|
||||||
|
|
||||||
|
[<OneTimeTearDown>]
|
||||||
|
let ``Update all tests`` () =
|
||||||
|
GlobalBuilderConfig.updateAllSnapshots ()
|
||||||
|
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let ``simple list test`` () =
|
let ``simple list test`` () =
|
||||||
expect {
|
expect {
|
||||||
snapshotList [ "1" ; "2" ; "3" ]
|
snapshotList []
|
||||||
return [ 1..3 ]
|
return [ 1..3 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let ``list test with formatting`` () =
|
let ``list test with formatting`` () =
|
||||||
expect {
|
expect {
|
||||||
snapshotList [ "8" ; "9" ; "0" ; "1" ; "2" ]
|
snapshotList []
|
||||||
withFormat (fun x -> string<int> (x % 10))
|
withFormat (fun x -> string<int> (x % 10))
|
||||||
return [ 8..12 ]
|
return [ 8..12 ]
|
||||||
}
|
}
|
||||||
|
@@ -17,8 +17,6 @@ type Mode =
|
|||||||
| Update
|
| Update
|
||||||
| AssertMockingSource of (string * int)
|
| AssertMockingSource of (string * int)
|
||||||
|
|
||||||
type BuilderKindNormal<'T> = | BuilderKindNormal of unit
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The builder which powers WoofWare.Expect.
|
/// The builder which powers WoofWare.Expect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,6 +33,7 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
else
|
else
|
||||||
ExpectBuilder Mode.Assert
|
ExpectBuilder Mode.Assert
|
||||||
|
|
||||||
|
/// Combine two `ExpectStateListy`s. The first one is the "expected" snapshot; the second is the "actual".
|
||||||
member _.Bind<'U> (state : ExpectStateListy<'U>, f : unit -> ExpectStateListy<'U>) : ExpectStateListy<'U> =
|
member _.Bind<'U> (state : ExpectStateListy<'U>, f : unit -> ExpectStateListy<'U>) : ExpectStateListy<'U> =
|
||||||
let actual = f ()
|
let actual = f ()
|
||||||
|
|
||||||
@@ -58,6 +57,8 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Actual = actual.Actual
|
Actual = actual.Actual
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combine an `ExpectStateListy` with an `ExpectState`. The first one is the "expected" snapshot; the second is
|
||||||
|
/// the "actual".
|
||||||
member _.Bind<'U, 'elt when 'U :> IEnumerable<'elt>>
|
member _.Bind<'U, 'elt when 'U :> IEnumerable<'elt>>
|
||||||
(state : ExpectStateListy<'elt>, f : unit -> ExpectState<'U>)
|
(state : ExpectStateListy<'elt>, f : unit -> ExpectState<'U>)
|
||||||
: ExpectStateListy<'elt>
|
: ExpectStateListy<'elt>
|
||||||
@@ -126,6 +127,7 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
JsonDocOptions = jsonDocOptions
|
JsonDocOptions = jsonDocOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Express that the actual value's <c>ToString</c> should identically equal this string.</summary>
|
||||||
[<CustomOperation("snapshot", MaintainsVariableSpaceUsingBind = true)>]
|
[<CustomOperation("snapshot", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
member _.Snapshot<'a>
|
member _.Snapshot<'a>
|
||||||
(
|
(
|
||||||
@@ -190,6 +192,13 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Actual = None
|
Actual = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that the actual value, when converted to JSON, should result in a JSON document
|
||||||
|
/// which matches the JSON document that is this string.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For example, <c>snapshotJson "123"</c> indicates the JSON integer 123.
|
||||||
|
/// </remarks>
|
||||||
[<CustomOperation("snapshotJson", MaintainsVariableSpaceUsingBind = true)>]
|
[<CustomOperation("snapshotJson", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
member this.SnapshotJson<'a>
|
member this.SnapshotJson<'a>
|
||||||
(
|
(
|
||||||
@@ -263,6 +272,13 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Actual = None
|
Actual = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that the actual value, which is a sequence, should have elements which individually (in order) match
|
||||||
|
/// this snapshot list.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For example, <c>snapshotList ["123" ; "456"]</c> indicates an exactly-two-element list <c>[123 ; 456]</c>.
|
||||||
|
/// </remarks>
|
||||||
[<CustomOperation("snapshotList", MaintainsVariableSpaceUsingBind = true)>]
|
[<CustomOperation("snapshotList", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
member _.SnapshotList<'a>
|
member _.SnapshotList<'a>
|
||||||
(
|
(
|
||||||
@@ -340,6 +356,17 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Actual = None
|
Actual = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expresses that the given expression throws during evaluation.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// expect {
|
||||||
|
/// snapshotThrows @"System.Exception: oh no"
|
||||||
|
/// return! (fun () -> failwith "oh no")
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
[<CustomOperation("snapshotThrows", MaintainsVariableSpaceUsingBind = true)>]
|
[<CustomOperation("snapshotThrows", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
member _.SnapshotThrows<'a>
|
member _.SnapshotThrows<'a>
|
||||||
(
|
(
|
||||||
@@ -377,7 +404,6 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
||||||
/// comparing to the snapshot.
|
/// comparing to the snapshot.
|
||||||
/// this value.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
||||||
@@ -391,6 +417,14 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Formatter = Some (fun f -> f () |> formatter)
|
Formatter = Some (fun f -> f () |> formatter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
||||||
|
/// comparing to the snapshot.
|
||||||
|
/// In the case of <c>snapshotList</c>, this applies to the elements of the sequence, not to the sequence itself.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
||||||
|
/// </remarks>
|
||||||
[<CustomOperation("withFormat", MaintainsVariableSpaceUsingBind = true)>]
|
[<CustomOperation("withFormat", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
member _.WithFormat<'T> (state : ExpectStateListy<'T>, formatter : 'T -> string) =
|
member _.WithFormat<'T> (state : ExpectStateListy<'T>, formatter : 'T -> string) =
|
||||||
match state.Formatter with
|
match state.Formatter with
|
||||||
@@ -423,6 +457,20 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
JsonSerialiserOptions = Some jsonOptions
|
JsonSerialiserOptions = Some jsonOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that these JsonSerializerOptions should be used to construct the JSON object to which the snapshot
|
||||||
|
/// is to be compared (or, in write-out-the-snapshot mode, to construct the JSON object to be written out).
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// If you want your snapshots to be written out compactly, rather than the default indenting:
|
||||||
|
/// <code>
|
||||||
|
/// expect {
|
||||||
|
/// snapshotJson @"{""a"":3}"
|
||||||
|
/// withJsonSerializerOptions (JsonSerializerOptions (WriteIndented = false))
|
||||||
|
/// return Map.ofList ["a", 3]
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
[<CustomOperation("withJsonSerializerOptions", MaintainsVariableSpaceUsingBind = true)>]
|
[<CustomOperation("withJsonSerializerOptions", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
member _.WithJsonSerializerOptions<'T> (state : ExpectStateListy<'T>, jsonOptions : JsonSerializerOptions) =
|
member _.WithJsonSerializerOptions<'T> (state : ExpectStateListy<'T>, jsonOptions : JsonSerializerOptions) =
|
||||||
match state.Snapshot with
|
match state.Snapshot with
|
||||||
@@ -506,6 +554,7 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
/// Computation expression `Delay`.
|
/// Computation expression `Delay`.
|
||||||
member _.Delay (f : unit -> ExpectStateListy<'T>) : unit -> ExpectStateListy<'T> = f
|
member _.Delay (f : unit -> ExpectStateListy<'T>) : unit -> ExpectStateListy<'T> = f
|
||||||
|
|
||||||
|
/// Computation expression `Run`, which runs a `Delay`ed snapshot assertion, throwing if the assertion fails.
|
||||||
member _.Run (f : unit -> ExpectStateListy<'T>) : unit =
|
member _.Run (f : unit -> ExpectStateListy<'T>) : unit =
|
||||||
let state = f ()
|
let state = f ()
|
||||||
|
|
||||||
@@ -528,14 +577,16 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
|
|
||||||
if snapshot <> actual then
|
if snapshot <> actual then
|
||||||
let diff =
|
let diff =
|
||||||
Diff.patienceLines (Array.ofList snapshot) (Array.ofList actual)
|
Diff.patienceLines (Array.ofList snapshot) (Array.ofList actual) |> Diff.format
|
||||||
|> Diff.format
|
|
||||||
|
|
||||||
match mode with
|
match mode with
|
||||||
| Mode.Assert ->
|
| Mode.Assert ->
|
||||||
$"snapshot mismatch! snapshot at %s{caller.FilePath}:%i{caller.LineNumber} (%s{caller.MemberName}) diff:\n%s{diff}"
|
if GlobalBuilderConfig.isBulkUpdateMode () then
|
||||||
|> ExpectException
|
GlobalBuilderConfig.registerTest state
|
||||||
|> raise
|
else
|
||||||
|
$"snapshot mismatch! snapshot at %s{caller.FilePath}:%i{caller.LineNumber} (%s{caller.MemberName}) diff:\n%s{diff}"
|
||||||
|
|> ExpectException
|
||||||
|
|> raise
|
||||||
| Mode.AssertMockingSource (mockSource, line) ->
|
| Mode.AssertMockingSource (mockSource, line) ->
|
||||||
$"snapshot mismatch! snapshot at %s{mockSource}:%i{line} (%s{caller.MemberName}) diff:\n%s{diff}"
|
$"snapshot mismatch! snapshot at %s{mockSource}:%i{line} (%s{caller.MemberName}) diff:\n%s{diff}"
|
||||||
|> ExpectException
|
|> ExpectException
|
||||||
|
@@ -44,6 +44,7 @@ type internal UniqueLines<'line when 'line : comparison> =
|
|||||||
LineCounts : Map<'line, int>
|
LineCounts : Map<'line, int>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The diff between two line-oriented pieces of text.
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Diff =
|
module Diff =
|
||||||
/// Find lines that appear exactly once in a sequence
|
/// Find lines that appear exactly once in a sequence
|
||||||
@@ -260,6 +261,7 @@ module Diff =
|
|||||||
)
|
)
|
||||||
|> String.concat "\n"
|
|> String.concat "\n"
|
||||||
|
|
||||||
|
/// Format the diff as a human-readable string, including line numbers at the left.
|
||||||
let formatWithLineNumbers (d : Diff) : string = formatWithLineNumbers' id d
|
let formatWithLineNumbers (d : Diff) : string = formatWithLineNumbers' id d
|
||||||
|
|
||||||
/// Format the diff as a human-readable string.
|
/// Format the diff as a human-readable string.
|
||||||
|
Reference in New Issue
Block a user