Pester
Pester copied to clipboard
-HaveCount assertion operator does not work when using strong typing on $null
1. Provide a general summary of the issue in the Title above
Pester 4.2.0 introduced a new -HasCount assertion operator. When migrating existing tests, by replacing $variable.Count | Should -Be $expectedCount to $variable | Should -HaveCount $expectedCount, I found edge cases where this does not work. I created Pester tests to demonstrate this behaviour in a simple case but later on upon request I can also give 2 more comples examples where -HasCount does not work. The idea is to first investigate/fix the first simple problem and only afterwards approach the more complex test cases.
Describe "Pesters -HaveCount operator should not fail on `$null object that has Count property" {
It "when not using strong typing" {
$aliases = Get-Alias | Select-Object -First 0
$aliases.Count | Should -Be 0
$aliases | Should -HaveCount 0
}
It "when LHS is casted to [System.Array]" {
[System.Array] $aliases = Get-Alias | Select-Object -First 0
$aliases.Count | Should -Be 0
$aliases | Should -HaveCount 0
}
It "when RHS is casted to [System.Array]" {
$aliases = [System.Array] (Get-Alias | Select-Object -First 0)
$aliases.Count | Should -Be 0
$aliases | Should -HaveCount 0
}
It "when LHS and RHS are casted to [System.Array]" {
[System.Array] $aliases = [System.Array] (Get-Alias | Select-Object -First 0)
$aliases.Count | Should -Be 0
$aliases | Should -HaveCount 0
}
}
2. Describe Your Environment
Pester version : 4.3.1 PowerShell version : 5.1.16299.248 PowerShell version : 6.0.1 PowerShell version : 6.1.0-preview.589 OS version : Microsoft Windows NT 10.0.16299.0
3. Expected Behavior
All 4 tests above should pass
4.Current Behavior
The first test passed but not the other 3. By debugging it, the objects seem to be of the same type ($null), therefore I cannot explain why Pester behaves differently. PowerShell has .Count property on $null by design.
Describing Pesters -HaveCount operator should not fail on $null object that has Count property
[+] when not using strong typing 387ms
[-] when LHS is casted to [System.Array] 132ms
Expected an empty collection, but got collection with size 1 @().
11: $aliases | Should -HaveCount 0
at <ScriptBlock>, C:\Users\cberg\git\bla.tests.ps1: line 11
[-] when RHS is casted to [System.Array] 105ms
Expected an empty collection, but got collection with size 1 @().
17: $aliases | Should -HaveCount 0
at <ScriptBlock>, C:\Users\cberg\git\bla.tests.ps1: line 17
[-] when LHS and RHS are casted to [System.Array] 37ms
Expected an empty collection, but got collection with size 1 @().
23: $aliases | Should -HaveCount 0
at <ScriptBlock>, C:\Users\cberg\git\bla.tests.ps1: line 23
Tests completed in 662ms
Tests Passed: 1, Failed: 3, Skipped: 0, Pending: 0, Inconclusive: 0
5. Possible Solution
Document more clearly on what types the new HasCount assertion operator works but to me it rather looks like a Pester internal bug that needs to be resolved.
6. Context
As described in paragraph: migrating to new -HasCount operator, i.e. not a blocking issue.
similarly.. I would expect this to work on HashTable and Dictionaries to could the number of Items in the dictionary.
Describe "HaveCount issues" {
It "Should Work with Dctionary<TKey, TValue>" {
$Dictionary = [System.Collections.Generic.Dictionary[String,String]]::new()
$Dictionary | Should -HaveCount 0
}
It "Should Work with HashTable" {
$HashTable = @{}
$HashTable | Should -HaveCount 0
}
}
Result:
Describing HaveCount issues
[-] Should Work with Dctionary<TKey, TValue> 114ms
Expected an empty collection, but got collection with size 1 @(System.Collections.Generic.Dictionary`2[System.String,System.String]).
4: $Dictionary | Should -HaveCount 0
[-] Should Work with HashTable 74ms
Expected an empty collection, but got collection with size 1 @(System.Collections.Hashtable).
8: $HashTable | Should -HaveCount 0
Yes, I have seen similar issues with different .net collection types as well but decided to rather raise this simple one first.
As the count property on objects returns non intuitive results as described above, I'd change $ActualValue.Count to ($ActualValue | Measure-Object).count This should fix the issues above.
Although a different solution would need to be included for PoSh 2.0 as measure-object was introduced in 3.0
The original issue is mostly a PowerShell-issue of iterating $null through pipeline. MS fixed the same issue with foreach ($a in $null) { # ran once pre-v3 }-loop in PSv3, but this one is still there:
$three = [System.Array] (1..3 | Where-Object { $false })
[System.Array] $four = [System.Array] (1..3 | Where-Object { $false })
$three | ForEach-Object { "hello three" }
$four | ForEach-Object { "hello four" }
# Output:
hello three
hello four
Now that PowerShell v2 supports is dropped, the suggested fix with Measure-Object can be considered as it will fix these edge-cases. Doesn't look like it affects performance that much:
> $p = (dir)*100
> $p.Length
3600
> (Measure-Command { 1..10 | % { $p.Count } }).TotalMilliseconds
7,8107
> (Measure-Command { 1..10 | % { ($p | Measure-Object).Count } }).TotalMilliseconds
68,4193
As for the other issues mentioned here:
- #1448 covers this issue with null not being showed in the error. It should've said
...but got collection with size 1 @($null).. -HaveCountwith hashtable/dictionaries are covered in #1200 and #1234.
10 times slower seems like a lot. But it is a start.
It was even worse in my Unix container tbh, but with real life scenarios (fewer calls? Smaller collections?) it might not affect the total that much.
We can probably scratch this approach as I just noticed it's a breaking change. It doesn't just ignore these edge cases, it drops null completely. Measure-Object will break @(1,2,$null,4) | Should -HaveCount 4
I think the biggest issue here is the lack of null in the formatted error-message (Update: This is fixed in 5.3.0-alpha2)