pester / Pester

Pester is the ubiquitous test and mock framework for PowerShell.
https://pester.dev/
Other
3.08k stars 470 forks source link

Support deep (recursive) equality on object properties with Should -Be #2245

Closed hmmwhatsthisdo closed 1 year ago

hmmwhatsthisdo commented 2 years ago

Checklist

Summary of the feature request

Currently, there's no (built-in) method for performing object comparison with Should. This is especially cumbersome when trying to test code that emits complex object types (be it [PSCustomObject] or another type)

Consider the following test case:

([PSCustomObject]@{
    Foo = "Bar"
    Bar = "Baz"
}) | Should -Be ([PSCustomObject]@{
    Foo = "Bar"
    Bar = "Baz"
})

These two objects appear similar, but as they're reference types Pester will perform a reference comparison, which will return $false.

It seems like others have run into this sort of situation in the past. The consensus seems to revolve around doing one of the following:

These all come with their own caveats:

To remedy this, the recursive comparison already available for arrays should be expanded to object properties as well.

How should it work?

This could be implemented as an additional parameter on -Be (e.g. -DeepEquality), or as a separate assertion type altogether (-BeDeeplyEqual) if users should be able to select reference or deep equality comparisons.

For instance:

$v1 = [PSCustomObject]@{
    Foo = "Bar"
    Bar = "Baz"
}
$v2 = [PSCustomObject]@{
    Foo = "Bar"
    Bar = "Baz"
}

$v1 | Should -Be $v2 -DeepEquality # passes
nohwnd commented 2 years ago

https://github.com/nohwnd/Assert

Has deep equality and deep equivalency feature, and the output is better, not worse than Pesters own output.

In fact -Because was adopted into Pester from Assert, which got the inspiration from FluentAssertions. Same for the Format-Nicely function that I wrote for Assert, and then later re-used a trimmed down version of it in Should.

Integrating Assert into Pester is being talked about for a while, but no-one did the work yet. It is not technically difficult, it is just a lot of work to make it all work the same. Even the best course of action would be to replace the current Should with new better assertions from Assert, that don't mix stuff like specialized assertion for strings with multi purpose assertions for objects.

The existing module also has useful features like comparing only what is in your expected object, so you don't have to specify everything on the incoming object.

https://www.youtube.com/watch?v=iGEFqRLwdzg Here is a talk from while ago on the module, not much changed since them I believe.

hmmwhatsthisdo commented 2 years ago

Ah, very interesting! I take it Assert's output format/API is directly compatible with Pester assertions?

nohwnd commented 2 years ago

Not in a way that Assert would be plugin to the Pester Should function, but yes, I guess.

Assertions are just code that throws exception when some criteria are not met.

function Is1($Value) { 
    if (1 -ne $Value) { throw "Expected 1, but got $Value" }
}

This is a valid assertion to use with Pester. Pester does not impose any criteria on how assertions should work, it only needs the assertion to play nicely for additional "advanced" features, like collecting multiple failures per test and managing test result (e.g. setting it as inconclusive).

So any collection of such functions can be considered a library of compatible assertions for Pester.

If you are asking about the output being similar to Pester's Should output, I think it is. Because Pester Should is now based on similar codebase, using similar formatting functions.

hmmwhatsthisdo commented 2 years ago

I see - based on the data contract for custom assertions, I was under the impression that assertions needed to respond with a certain-shaped response (exception types, properties, etc.) in order for Pester to distinguish "test errored out" vs "test failed".

If simply throwing an exception is enough (and Assert's exceptions are appropriately verbose enough to fit the bill here) then I can simply use those for the time being.

Thanks!