pester / Pester

Pester is the ubiquitous test and mock framework for PowerShell.
https://pester.dev/
Other
3.08k stars 470 forks source link

Is it possible to name a mocked function and verify if it was called? #1709

Open DarkLite1 opened 3 years ago

DarkLite1 commented 3 years ago

1. General summary of the issue

I couldn't really find an example in the docs so I'll ask my question here.

In the code below it is asserted if the function Get-Process is called with a specific ParameterFilter. This works fine but leaves me with the following questions:

$GetProcessSvcHost = Mock Get-Process -Verifiable -ParameterFilter {
    ($Name -eq "svchost") 
} -MockWith {
    [PSCustomObject]@{Name = 'svchost'; Id = 1 }
    [PSCustomObject]@{Name = 'svchost'; Id = 2 }
}
. $testScript
Should -InvokeMockName 'GetProcessSvcHost' -Exactly -Times 1

Code example

# Process.ps1
$svcHostProcesses = Get-Process -Name 'svchost'
# Process.Tests.ps1
$testScript = $PSCommandPath.Replace('.Tests.ps1', '.ps1')

Describe 'the script should' {
    BeforeAll {
        Mock Get-Process
    }
    It 'get all processes with name svchost' {
        Mock Get-Process -Verifiable -ParameterFilter {
            ($Name -eq "svchost") 
        } -MockWith {
            [PSCustomObject]@{Name = 'svchost'; Id = 1 }
            [PSCustomObject]@{Name = 'svchost'; Id = 2 }
        }

        . $testScript

        Should -InvokeVerifiable
        $svcHostProcesses.Id |  Should -BeExactly @(1, 2)
    }
}

2. Describe Your Environment

Pester version     : 5.0.4 C:\Program Files\WindowsPowerShell\Modules\Pester\5.0.4\Pester.psd1
PowerShell version : 5.1.14393.3866
OS version         : Microsoft Windows NT 10.0.14393.0
nohwnd commented 3 years ago

How can we verify if this mock was only called once and not multiple times?

Use -Exactly

Would it be possible to name a mock? So we can do something like:

It would be possible, but if I were implementing it I would just use the reference to a mock object and validate on that, but this is usually not needed. As you shown verifiable mock is similar thing to this.

I can think about very few cases where this would be useful. You would need to have different behavior for different parameter filters, and then assert on those different behaviors.

Much like you show in your example, have multiple different mocked ways to call get-service, and then verify them.

I don't have any plans to implement this at the moment, but this would be probably the shape:

# this would output a hashtable that you can splat over the mock, maybe some kind of Id would be involved
$getProcessMock = Mock Get-Process -ParameterFilter { $Name -eq 'Idle' } -PassThru

# <your test code>

# splat the params to Should -Invoke
Should -Invoke @getProcessMock -Exactly 1

Just a quick idea, nothing final. But as I said, no interest in implementing this myself at the moment.

DarkLite1 commented 3 years ago

When I try your suggestion it fails with the message that the arguments are incorrect:

Should -InvokeVerifiable -Exactly -Times 1

Also, when I test multiple Pester *.Tests.ps1 files I've noticed it breaks Pester. I think there might be an issue with starting every time with a clean slate. Some session/state data is interfering with subsequent tests. But that's another issue ... I'll have to create a duplication case for you so you can see if for yourself. Another strange thing is that I sometimes get the error:

RuntimeException: The expression after '.' in a pipeline element produced an object that was not valid. It must result in a command name, a script block, or a CommandInfo object.

Really weird because it worked before.... Hmmm... Need to reproduce this for you.

nohwnd commented 3 years ago

Yeah that won't work with -InvokeVerifiable, just with -Invoke.

DarkLite1 commented 3 years ago

That was my issue, I can't seem to specify which mock needs to be checked for invocation.

I understand this is not high on your prio list. Let's just keep this one open until someone (or me) finds the time to implement this.

nohwnd commented 3 years ago

You can't specify it with verifiable, because the whole point of verifiable is to validate if all mocks were called. Personally I never use verifiable.

You can thought just specify to check all calls that have the parameterFilter you used for the mocking and check if there is exactly 1 matching call.