I am trying to test code that calls on external binaries (in my example certreq and openssl.exe). Obviously I want to mock these calls in my tests but when I try, Pester complains:
You did not declare a mock of the certreq Command.
Here's a small reproducable example:
Mock echo { }
echo foo
Assert-MockCalled echo
Running Powershell 3.0 on Windows 7.
The echo is an alias of Write-Output so you need to assert-mockcalled Write-Output not echo to make it work.
But you still may mock echo and call echo in the sut.
Well echo was just an example. But in my case I want to mock certreq and ../vendor/openssl.exe. I get the same error: Assert-MockCalled complains that I never mocked the command.
Mocking external commands is really flaky, unfortunately. My personal preference is to wrap all calls to those commands in PowerShell functions, and then I can easily mock calls to those functions instead. For example:
function Invoke-CertReq
{
certreq.exe @args
}
Well, I was afraid of that. Thanks anyway. :)
Ok, follow up to your suggestion: how do I use this to verify parameters sent to the Invoke-CertReq function?
Mock Invoke-CertReq -ParameterFilter { ??? }
I'd probably do some analysis of the default $args variable inside the ParameterFilter script block. You'd have this same problem trying to mock the external command directly, though. Anything that's not a PowerShell command, has no parameter metadata to make it easier on you.
Regarding the parameterfiltering, I did like this to mock a call to an external program.
function Invoke-Nuget
{
& "/tools/nuget.exe" $args
}
Invoke-Nuget list foo.bar -prerelease
In my test i do like this:
Assert-MockCalled -CommandName Invoke-Nuget -Times 1 -ParameterFilter {$args[0] -eq "list" -and $args[1] -eq "foo.bar" -and $args[2] -eq "-prerelease"}
This works fine.
@kajbonfils Are you showing a solution or asking a question? :)
@nohwnd Sorry, that wasn't clear. It was a solution. I have updated the response.
I assume that the issue can be closed now. Feel free to reopen if you need more assistance.
Can we get some clarification on the final answer? Is wrapping the native executable and testing against the wrapper the recommended method at this point?
Yes, wrapping native commands to functions and mocking functions is recommended way.
@nohwnd et al, is there additional guidance on this for Pester 5 and mocking az / databricks / kubectl / jq cli tools and other python, go or exe/com external cli tools?
Examples of being able to pester test python / go cli tools such as these would be useful, esp within powershellcore.
Creating a wrapper for mocking each cli tool's callout seems a bit inefficient and a workaround to accommodate the testing framework.
Nice! This seems to work, since the cli tools are nuget/pip installed packages.
Mock 'jq.exe' -MockWith { $true }
Mock 'databricks.exe' -MockWith { $true }
exe extension is optional?
@asears Yes, it is possible since forever, but the version without .exe works since https://github.com/pester/Pester/issues/1043
Hi!
If I want to mock an external command with a parameter, e.g. this
qwinsta /server localhost
Is this possible without using a wrapper?
(I've managed to make it work for a parameterless call to qwinsta, but once I added the arguments the mock stopped working).
(I'm using Pester 5)
how does your mock look like?
Thank you for your reply/question. However, I think the problem I experienced with external mocks was related to the issue I reported earlier today: https://github.com/pester/Pester/issues/1696. The problem, which I wasn't able to diagnose at that time, seems to be related to a non-empty scriptblock (as described in #1696).
However, the failure output is a little different, so I prepared a minimal example to trigger it, in case it would prove helpful.
BeforeAll {
function FunctionThatInvokesQwinsta ($Server='localhost') {
qwinsta /server $Server
}
}
Describe "Mocking external command bug?" {
It 'works for mock with empty scriptblock' {
Mock qwinsta { }
FunctionThatInvokesQwinsta | Should -Invoke qwinsta
}
It 'reports CommandNotFoundException when non-empty scriptblock' {
Mock qwinsta { "Some output" }
FunctionThatInvokesQwinsta | Should -Invoke qwinsta
}
}
The output is
[-] Mocking external command bug?.reports CommandNotFoundException when non-empty scriptblock 104ms (103ms|1ms)
CommandNotFoundException: Could not find Command Some output
Tests completed in 258ms
Tests Passed: 1, Failed: 1, Skipped: 0 NotRun: 0
Most helpful comment
Regarding the parameterfiltering, I did like this to mock a call to an external program.
In my test i do like this:
Assert-MockCalled -CommandName Invoke-Nuget -Times 1 -ParameterFilter {$args[0] -eq "list" -and $args[1] -eq "foo.bar" -and $args[2] -eq "-prerelease"}This works fine.