In Pester I need to mock a pipeline function that creates a hashtable in the begin block, updates it in the process block, and returns it in the end block.
I thought it would be a simple case of adding an end block to the mock and returning a hashtable from that. However, it appears the mocked end block is called for each item in the pipeline, rather than after all the pipeline items have been passed. So the mock produces an array of hashtables, rather than a single hashtable.
How can I mock a pipeline function to return a single hashtable after all the pipeline items have been passed to it?
Here's sample code that illustrates the problem:
BeforeAll {
function Get-NameInfo (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$FeatureName
)
{
begin
{
$result = @{
InputNames = @()
InputCount = 0
}
}
process
{
$result.InputNames += $FeatureName
$result.InputCount++
}
end
{
return $result
}
}
function Get-Result ([array]$ArrayOfNames)
{
$result = $ArrayOfNames | Get-NameInfo
return $result
}
}
Describe 'Get-Result' {
BeforeAll {
Mock Get-NameInfo {
end
{
return @{ InputNames = @(); InputCount = 0 }
}
}
}
BeforeEach {
$names = @('Name 1', 'Name 2', 'Name 3', 'Name 4')
}
It 'doesn''t return an array' {
$result = Get-Result $names
# Produces the following error:
# "Expected $false, but got $true."
$result -is [System.Object[]] | Should -BeFalse
}
}
When I call the functions manually, outside of Pester, the function Get-NameInfo
returns a single hashtable. However, the mocked version of the function returns an array of hashtables.
The following screenshot shows that the $result
returned from the mocked function is an array of four identical hashtables:
This appears to be a limitation of Pester at the moment (as of version 5.6.1) per the comments from a maintainer in this issue:
This is a long-standing limitation of Mock. It is not impossible. Just complicated to do.
It seems to be to do with the fact that the way Mock
works currently utilises the begin
..process
..end
pattern as part of the mock framework:
Okay, been looking a bit more into the implementation of Mock, what we do now is that in begin we setup the mock, in process we run the MockWith scriptblock for every item that was received via pipeline, and in end we just check if we should invoke the original command, and if yes we invoke it.
Which means that when you're mocking a function that accepts pipeline input, regardless of whether or not you define a begin..process..end in your mock the code you define is all executed as part of the process block.
I think your best option to workaround this limitation at the moment would be to either not Mock the function at all (just execute it), or create a stub function (e.g just define a version of the function in your test script, as you have done in your example) that executes a cut down version of the function in a way that simulates the output for your test scenario (there may not be any way to cut down your function if it's already relatively simple).