0

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:

  • swapping the members of the equal comparison in the predicate scriptblock { $inputName -eq $Name}

  • using $ExecutionContext.InvokeCommand.ExpandString($inputName) in the script block to resolve $inputName string

  • creating a script block with [ScriptBlock]::create($Name -eq $inputName) then using this in the -ParameterFilter

  • last but not least I tried to call GetNewClosure of my script block, but it did not help either: { $Name -eq $inputName}.GetNewClosure()

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

mirind4
  • 1,423
  • 4
  • 21
  • 29
  • Does this answer your question? [How to setup a Pester mock with arguments from a function](https://stackoverflow.com/questions/68447967/how-to-setup-a-pester-mock-with-arguments-from-a-function) – Daniel Sep 16 '21 at 22:48

1 Answers1

1

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 { }

Daniel
  • 4,792
  • 2
  • 7
  • 20