mirind4
mirind4

Reputation: 1573

String parameter is not resolved when parameterizing Mock ParameterFilter in Pester

I am using Pester testing library (with version 5.0.2) to test my PowerShell scripts (with version 5.1) and mock its dependencies.

Pester has a Mock method which can be used to mock dependencies. More info here.

I am trying to create a helper method wrapping this Mock method, to make my code more readable:

    Function MockVstsInput {
        Param(
            [Parameter(Mandatory=$true, Position=1)]
            [string]$inputName,
            [Parameter(Mandatory=$true, Position=2)]
            [string]$returnValue
        )

        Mock Get-VstsInput {return $returnValue} -ParameterFilter { $Name -eq $inputName}
    }

In this helper method I am mocking the dependency Get-VstsInput which has a parameter $Name

Then I use this code in my test script:

    MockVstsInput "targetapi" "api-name"

Meaning that if the Get-VstsInput is called with $Name param "targetapi" then it should return "api-name". Usually such parameterizing works in other languages (f.e.: C# or Java), but here the $inputName string is not resolved in the MockVstsInput method.

When I call the Get-VstsInput method in my production code:

    $newapi=Get-VstsInput -Name targetapi

Then in the log I have the following Mock information:

Mock: Running mock filter {  $Name -eq $inputName } with context: Name = targetapi. 
Mock: Mock filter did not pass. 

Where we can see that the $inputName string is not resolved in my scriptblock, so the mocking does not happen.

What I have tried so far, with no success:

What do you think what the root cause of my problem is? Thanks in advance for all the help!

Upvotes: 0

Views: 458

Answers (1)

Daniel
Daniel

Reputation: 5114

You are close. It's all in how you build the needed scriptblocks.

For the ParameterFilter scriptblock you need to escape $Name with a backtick so that it gets created as a variable. $inputName will be replaced with our variable value so you need to surround in quotes.

# $inputName = 'Daniel'
$sb_param = [scriptblock]::Create("`$Name -eq '$inputName'")

This way the final statement inside the scriptblock looks like

{ $Name -eq 'Daniel' }

Similarly, in our MockWith block, we need to also surround with quotes or the scriptblock will not include a string, but an invalid command

# $returnValue = 'Hi Daniel'
$sb_return = [scriptblock]::Create("'$returnValue'")

becomes

{ 'Hi Daniel' }

if we don't include the quotes when we create the scriptblock, the scriptblock will contain the statement Hi Daniel which will fail as command not found


Here is everything in a working example

Describe 'Setting up a Mock from a Function' {
    BeforeAll {
        Function Get-VstsInput {
            [CmdletBinding()]
            Param(
                $Name
            )
            'Not Mocked'
        }

        Function MockVstsInput {
            Param(
                [Parameter(Mandatory = $true, Position = 1)]
                [string]$inputName,
                [Parameter(Mandatory = $true, Position = 2)]
                [string]$returnValue
            )
            $sb_param = [scriptblock]::Create("`$Name -eq '$inputName'")
            $sb_return = [scriptblock]::Create("'$returnValue'")
            Mock Get-VstsInput -MockWith $sb_return -ParameterFilter $sb_param
        }
        MockVstsInput -inputname 'Daniel' -returnValue 'Hi Daniel'
    }

    It 'Should mock' {
        $test = Get-VstsInput -Name 'Daniel'
        $test | Should -Be 'Hi Daniel'
    }
}


To clarify, this is a scoping issue. What is happening is that you are providing these scriptblocks to the mock command to run at a later time in a different scope. When you run the mocked command the variables $inputName and $returnValue are not defined in those scriptblocks when they are invoked. These variables were only available in the MockVstsInput function and were cleaned up once the function completed.

To illustrate this, the following code will work, but I do not recommend doing this way because if you run the function more than once to define different Mocks you will be overriding the global variables each time affecting any previously defined Mocks

        Function MockVstsInput {
            Param(
                [Parameter(Mandatory = $true, Position = 1)]
                [string]$inputName,
                [Parameter(Mandatory = $true, Position = 2)]
                [string]$returnValue
            )           
            $global:mockInputName = $inputName
            $global:mockReturnValue = $returnValue
            Mock Get-VstsInput -ParameterFilter {$Name -eq $global:mockInputName} -MockWith {$global:mockReturnValue}
        }

So instead of giving the 2 parameters scriptblocks with variables to later be resolved which will no longer be in scope the solution is to create the scriptblocks with the values of our variables hardcoded.


Also, see this answer for a way to do it using BeforeEach { }

Upvotes: 1

Related Questions