I am receiving the following error when trying get this pester test to work for a function that receives a pipeline parameter.
"An error occured running Pester: The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input."
Module: MyModule.psm1
function MyFunction {
"piping to Write-Output" | Write-Output
$dataTable = New-Object System.Data.DataTable
$dataTable | MyPipelineFunction
}
function MyPipelineFunction {
[CmdletBinding()]
param (
[Parameter(
Mandatory = $true,
ValueFromPipeline=$true)]
[System.Data.DataTable] $DataTable
)
Write-Host "hello world"
}
Pester Test
Import-Module "C:\temp\StackOverflow\PipelineMock\MyModule\MyModule.psm1" -Force
InModuleScope 'MyModule' {
Describe 'MyFunction' {
BeforeAll {
# Create a sample DataTable
$dataTable = New-Object System.Data.DataTable
$dataTable.Columns.Add("Column1", [System.String])
$row = $dataTable.NewRow()
$row["Column1"] = "Sample Data"
$dataTable.Rows.Add($row)
# Mock the DataTable creation in MyInternalFunction
Mock -CommandName New-Object -ParameterFilter { $TypeName -eq 'System.Data.DataTable' } -MockWith { return $dataTable }
Mock MyPipelineFunction -MockWith {[CmdletBinding()] param (
[Parameter(
Mandatory = $true,
ValueFromPipeline=$true)]
[System.Data.DataTable] $DataTable
)
}
Mock Write-Output {}
}
It 'should call MyPipelineFunction' {
MyFunction
Assert-MockCalled -CommandName Write-Output -Exactly 1 -Scope It
Assert-MockCalled -CommandName New-Object -Exactly 1 -Scope It -ParameterFilter {$TypeName -eq 'System.Data.DataTable' }
Assert-MockCalled -CommandName MyPipelineFunction -Exactly 1 -Scope It -ParameterFilter {$DataTable -eq $dataTable}
}
}
}
I've several different Mock approaches and parameter filters given what I found out there. The Pester site doesn't seem to have any clear guidance on this. I was able to write a basic mock for Write-Output where I piped text to it and that works fine.
Can anyone see the problem?
Note that Assert-MockCalled
is obsolete, the replacement would be Should -Invoke
. See the documentation for details.
There 3 things you must know before showing you how the Pester code should look:
What Mathias mentioned in his comment, even tho DataTable
is not an IEnumerable
the pipeline will enumerate it, essentially by enumerating the DataRowCollection
, see Binders.cs#L613-L638
. So, If you want to pipe it, and your function to receive it as-is, you have to either use Write-Output -NoEnumerate
or the ,
operator:
function MyFunction {
'piping to Write-Output' | Write-Output
$dataTable = New-Object System.Data.DataTable
, $dataTable | MyPipelineFunction
}
The same consideration will apply in your Mock -CommandName New-Object
call, you will have to use -MockWith { , $dataTable }
there (notice the ,
before the variable).
This enumeration behavior mentioned for the pipeline also applies when Pester invokes the -MockWith
scriptblock, making its output to be enumerated, to put it visually:
$shouldBeDT = & {
$dataTable = New-Object System.Data.DataTable
$null = $dataTable.Columns.Add('Column1', [System.String])
$row = $dataTable.NewRow()
$row['Column1'] = 'Sample Data'
$dataTable.Rows.Add($row)
$dataTable
}
$shouldBeDT.GetType() # But is a `DataRow`
You should not define a param
block in your -MockWith
scriptblock, as suggested in the documentation:
NOTE: Do not specify param or dynamicparam blocks in this script block. These will be injected automatically based on the signature of the command being mocked, and the MockWith script block can contain references to the mocked commands parameter variables.
You're asserting that { $DataTable -eq $dataTable }
, you should note that PowerShell is a case-insensitive language, $DataTable
and $dataTable
refer to the same variable in this case. In addition, equality comparison using the -eq
operator won't work to tell if both, the DataTable used as argument for your function and the DataTable created in the BeforeAll
block are the same. In this case you could use [object]::ReferenceEquals
, however you will need to use a different variable name in your test.
In summary, the Pester test that should solve the problem:
InModuleScope 'MyModule' {
Describe 'MyFunction' {
BeforeAll {
$dt = New-Object System.Data.DataTable
$dt.Columns.Add('Column1', [System.String])
$row = $dt.NewRow()
$row['Column1'] = 'Sample Data'
$dt.Rows.Add($row)
# Mock the DataTable creation in MyInternalFunction
Mock New-Object -ParameterFilter { $TypeName -eq 'System.Data.DataTable' } -MockWith { , $dt }
Mock Write-Output {}
Mock MyPipelineFunction {}
}
It 'should call MyPipelineFunction' {
MyFunction
Assert-MockCalled Write-Output -Exactly 1 -Scope It
Assert-MockCalled New-Object -Exactly 1 -Scope It -ParameterFilter {
$TypeName -eq 'System.Data.DataTable'
}
Assert-MockCalled MyPipelineFunction -Exactly 1 -Scope It -ParameterFilter {
[object]::ReferenceEquals($DataTable, $dt)
}
}
}
}