Pester icon indicating copy to clipboard operation
Pester copied to clipboard

Better way to evaluate that a mock was called with specific parameter value

Open johlju opened this issue 3 years ago • 10 comments

Checklist

Summary of the feature request

As a user I am often validate if mocked command was called with the expected parameter value. It often goes like this:

It 'Should call the mock with the expected parameter value' {
    Mock -CommandName Get-MyInternalCommand

    Get-Something # Calls Get-MyInternalCommand internally

    Should -Invoke -CommandName Get-MyInternalCommand -ParameterFilter {
        $MyParameter -eq 'evaluate string value'
    }
}

If this fails it returns the unspecific error

Expected Get-MyInternalCommand in module X to be called at least 1 times but was called 0 times

How should it work?

Instead of the above and since we already have $PesterBoundParameters it would be great if we could get Pester to return a more specific error.

If we could get this to work:

It 'Should call the mock with the expected parameter value' {
    Mock -CommandName Get-MyInternalCommand

    Get-Something # Calls Get-MyInternalCommand internally

    Should -Invoke -CommandName Get-MyInternalCommand -UsingParameter 'MyParameter1' 
    Should -Invoke -CommandName Get-MyInternalCommand -UsingParameter 'MyParameter2' -WithValue 'evaluate string value'
}

If could then return more specific errors:

Expected Get-MyInternalCommand in module X to be called using the parameter MyParameter1 at least 1 times but was called 0 times.
Expected Get-MyInternalCommand in module X to be called using the parameter MyParameter2 with the value 'evaluate string value', but it was called with the value 'something else'.

johlju avatar Aug 05 '22 09:08 johlju

Thanks for the feedback!

Because we can't use parametersets in Should-operators, it might be confusing to see ParameterFilter and two new shortcut-parameters. Listing called values could also get messy quick with multiple calls.

Maybe include the expected value in the testname? There's also #2121 to fix -Because for -Invoke* which I'd prioritize.

fflaten avatar Aug 05 '22 10:08 fflaten

I've submitted a PR to enable -Because with -Invoke. Let me know if you don't consider that as a solution.

fflaten avatar Aug 05 '22 15:08 fflaten

It wouldn't be as clean, but I think it is better than how it works today (since it does not work at all today). 🙂

Would it be hard to make the parameter variables or $PSBoundParameters work in Because so it possible to do below?

    Should -Invoke -CommandName Get-MyInternalCommand -ParameterFilter {
        $MyParameter1 -eq 'string1'
    } -Because "expected the parameter MyParameter1 to have the value 'string1', but the actual value was <PSBoundParameters.MyParameter1>"

if not then it would not be optimal since it could not output the actual value which is important to know in this scenario.

    Should -Invoke -CommandName Get-MyInternalCommand -ParameterFilter {
        $MyParameter1 -eq 'string1'
    } -Because "expected the parameter MyParameter1 to have the value 'string1'"

Telling what the expected value should be is good, but then we still don't know what the value actually was, or was it $null. We still have to add a Write-Verbose -Verbose -Message $MyParameter1 to know what the actual value is (or do step debug) to understand mor why the test failed.

johlju avatar Aug 06 '22 11:08 johlju

Running with Diagnostic output will tell you the parameters used for each call when mock is invoked for troubleshooting.

Resolving the variable in -Because won't work well unfortunately. Should -Invoke evaluates the call history (multiple), not an individual mock call.

Printing the context (parameters) like we do in Diagnostic for all calls to the same CommandName+ModuleName is an option. It would get really noisy when parameters has longer values or complex objects, so not sure if I'd prefer a -ErrorIncludesCallHistory type of switch for that or simply refer to debug-output.

You can use Detailed output with only debug-logging for Mock to reduce some of the noise (Mock debug itself is talkative). @nohwnd ?

fflaten avatar Aug 06 '22 12:08 fflaten

Related #376

fflaten avatar Aug 09 '22 20:08 fflaten

Resolving the variable in -Because won't work well unfortunately. Should -Invoke evaluates the call history (multiple), not an individual mock call.

I guess we could dot source the filter and evaluate the because string after the filter finished running. We do that for the test name expansion.

Is this going to be a request that could pop up for Mock as well? Because there we would not know if we should evaluate in filter or the mock body.

nohwnd avatar Aug 11 '22 07:08 nohwnd

The root cause is that we cannot have parameter sets in the current Should so we can add parameters that are intuitive 🤔

What I understood by comments in other issues is that the goal is to move to something like Should-Invoke which can have parameter sets. Is that correct? In that case then I suggest we settle with the change that @fflaten done in the PR and wait to make this better in the future?

johlju avatar Aug 11 '22 10:08 johlju

I was commenting just on the quoted remark: Expanding

So yeah let's go with what frode implemented.

nohwnd avatar Aug 11 '22 12:08 nohwnd

I just tried this in a test of mine, and it seems to work pretty well. Is there any downside to this?

It 'Should call the mock with the expected parameter value' {
    Mock -CommandName Get-MyInternalCommand

    Get-Something # Calls Get-MyInternalCommand internally

    Should -Invoke -CommandName Get-MyInternalCommand -ParameterFilter {
        $MyParameter | Should -Be 'evaluate string value'
        $true
    }
}

johlju avatar Aug 22 '22 15:08 johlju

Is there any downside to this?

Not able to test atm. but I think it breaks Should.ErrorAction = 'Continue' if you use that. See this test

Also, see #1712

fflaten avatar Aug 22 '22 17:08 fflaten