admin管理员组

文章数量:1291077

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?

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?

Share Improve this question asked Feb 13 at 19:21 KurtisKurtis 374 bronze badges 4
  • Read pester.dev/docs/commands/Mock#-mockwith where it says "NOTE" ;) – Santiago Squarzon Commented Feb 13 at 19:31
  • The pipeline processor will automatically enumerate the rows from [DataTable] instances, so MyPipelineFunction never actually receives a [DataTable], but instead 0 or more [DataRow] objects. Change $dataTable | MyPipelineFunction to Write-Output $dataTable -NoEnumerate | MyPipelineFunction to see the difference. – Mathias R. Jessen Commented Feb 13 at 19:36
  • Are you suggesting I need to change the module function code "$dataTable | MyPipelineFunction" ? I did try your suggestion adding Write-Output but my only purpose of Write-Output mock in the original post was to show that I could mock that existing cmdlet where I passed a pipeline parameter to it. – Kurtis Commented Feb 13 at 20:17
  • I've also tried simplifying my Mocks in the test script Mock MyPipelineFunction -MockWith {} along with Assert-MockCalled -CommandName MyPipelineFunction -Exactly 1 -Scope It and this gives same error – Kurtis Commented Feb 13 at 20:18
Add a comment  | 

1 Answer 1

Reset to default 1

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:

  1. 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`
    
  2. 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.

  3. 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)
            }
        }
    }
}

本文标签: Trouble mocking Powershell function with Pester that takes a Pipeline parameterStack Overflow