You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Can't mock a function that takes pipeline input #2154
functionouter
{
1..3| inner
}
functioninner
{
param
(
[Parameter(ValueFromPipeline)]$InputObject
)
# implicit end block, runs once"RETURN"
}
Describe "unmocked" {
It "runs the end block" {
(outer).Count | Should -Be 1
}
}
Describe "mocked" {
It "runs the end block" {
Mock inner {"FOO"}
(outer).Count | Should -Be 1
}
}
Expected Behavior
Both tests pass
Current Behavior
Second test fails (because mock returns 1 object for each piped input)
Possible Solution? (optional)
Steppable pipeline. For sample code, run [System.Management.Automation.ProxyCommand]::Create([System.Management.Automation.CommandMetadata]::new((Get-Command New-Item)))
My comment was supposed to mean: It is technically possible to implement this into Mock, but it is difficult.
I am not 100% sure about the use cases for this, and how hard or easy it would be to keep it backwards compatible.
I quickly looked at the implementation yesterday, and we can know if the function has begin, process or end by looking at ast:
functionA {
begin
{
}
process
{
}
end
{
}
}
$a=Get-Command A
$a.ScriptBlock.Ast$a.ScriptBlock.Ast.BodyfunctionB {}
$b=Get-Command B
$b.ScriptBlock.Ast$b.ScriptBlock.Ast.Body
Where BeginBlock, ProcessBlock and EndBlock are populated.
I have to look at how steppable pipeline works because I've read about it many years ago but never had the need to use it since.
It would be nice to have multiple examples of how people want to use this before implementation is started, because honestly the Mock code is very complicated.
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.
From the examples above I am assuming that people want to do:
If there is just end block:
autodetect if the mocked function has begin and process and use mockWith to replace just that?
be able to assert (via Should -Invoke) on the number of calls of the end block?
If there is begin process and end:
replace the end block of the mocked function and get all the input that was processed by the pipeline?
My scenario is that I was trying to mock Set-Content, but it broke for 'a', 'b' | Set-Content .\test.txt. In my mock, I provided begin, process, and end blocks, which were called, but the problem is that the whole command was invoked twice (begin, process, end ; begin , process, end) instead of once with two calls to the process block (begin, process, process, end).
As a work-around you may be able to use Mock with -ParameterFilter to only return something from the mock when the last item is received from the pipeline, if you know what that last item is (which presumably you should do when writing tests).
Using the original example, modified to add a "mocked with ParameterFilter" Describe block that works:
BeforeAll {
functionouter
{
1..3| inner
}
functioninner
{
param
(
[Parameter(ValueFromPipeline)]$InputObject
)
# implicit end block, runs once"RETURN"
}
}
Describe "unmocked" {
It "runs the end block" {
(outer).Count | Should -Be 1
}
}
Describe "mocked" {
It "runs the end block" {
Mock inner {"FOO"}
(outer).Count | Should -Be 1
}
}
Describe "mocked with ParameterFilter" {
It "runs the end block" {
Mock inner { } -ParameterFilter { $InputObject-ne3 }
Mock inner {"FOO"} -ParameterFilter { $InputObject-eq3 }
(outer).Count | Should -Be 1
}
}
The "unmocked" and "mocked with ParameterFilter" tests both work. The "mocked" test fails since (outer).Count is 3.
Activity
nohwnd commentedon Apr 12, 2022
This is a long-standing limitation of Mock. It is not impossible. Just complicated to do.
Bigpop commentedon Sep 28, 2022
An alternative workaround may be to wrap a wrapper for testing.
JesseDarr commentedon Apr 18, 2023
Nowind, please explain your above comment. Is there documentation on how to do this? I can't seem to find any.
This may be complicated, but it's def a requirement if you want to use Pester to test any existing powercli code.
nohwnd commentedon May 10, 2023
My comment was supposed to mean: It is technically possible to implement this into Mock, but it is difficult.
I am not 100% sure about the use cases for this, and how hard or easy it would be to keep it backwards compatible.
I quickly looked at the implementation yesterday, and we can know if the function has begin, process or end by looking at ast:
Which outputs:
Where BeginBlock, ProcessBlock and EndBlock are populated.
I have to look at how steppable pipeline works because I've read about it many years ago but never had the need to use it since.
It would be nice to have multiple examples of how people want to use this before implementation is started, because honestly the Mock code is very complicated.
splatteredbits commentedon May 18, 2023
Here's an example. I'm trying to mock
RemoveDnsServerResourceRecord, which accepts objects returned byGet-DnsServerResourceRecordas input:nohwnd commentedon Jun 30, 2023
Okay, been looking a bit more into the implementation of Mock, what we do now is that in
beginwe setup the mock, inprocesswe run theMockWithscriptblock for every item that was received via pipeline, and inendwe just check if we should invoke the original command, and if yes we invoke it.From the examples above I am assuming that people want to do:
If there is just end block:
If there is begin process and end:
jazzdelightsme commentedon Nov 21, 2024
My scenario is that I was trying to mock
Set-Content, but it broke for'a', 'b' | Set-Content .\test.txt. In my mock, I providedbegin,process, andendblocks, which were called, but the problem is that the whole command was invoked twice (begin, process, end ; begin , process, end) instead of once with two calls to theprocessblock (begin, process, process, end).(Edit: tried on 5.6.1 and 6.0.0-alpha5)
SimonEData commentedon Jan 3, 2025
As a work-around you may be able to use
Mockwith-ParameterFilterto only return something from the mock when the last item is received from the pipeline, if you know what that last item is (which presumably you should do when writing tests).Using the original example, modified to add a "mocked with ParameterFilter" Describe block that works:
BeforeAll { function outer { 1..3 | inner } function inner { param ( [Parameter(ValueFromPipeline)]$InputObject ) # implicit end block, runs once "RETURN" } } Describe "unmocked" { It "runs the end block" { (outer).Count | Should -Be 1 } } Describe "mocked" { It "runs the end block" { Mock inner {"FOO"} (outer).Count | Should -Be 1 } } Describe "mocked with ParameterFilter" { It "runs the end block" { Mock inner { } -ParameterFilter { $InputObject -ne 3 } Mock inner {"FOO"} -ParameterFilter { $InputObject -eq 3 } (outer).Count | Should -Be 1 } }The "unmocked" and "mocked with ParameterFilter" tests both work. The "mocked" test fails since
(outer).Countis 3.powercode commentedon May 27, 2025
I have a use case for this which is to test that I pass the correct command to an invocation of pwsh.
I want to mock pwsh to validate the pipeline input.
test: Pester mocks use \$PesterBoundParameters + \$global: state