Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a call to an exe file with Pester?

Developing a script in PowerShell, I require to call an external executable file(.exe). currently I am developing this script with a TDD approach, therefore I require to mock the called to this .exe file.

I try this :

Describe "Create-NewObject" {
    Context "Create-Object" {
        It "Runs" {
            Mock '& "c:\temp\my.exe"' {return {$true}}
            Create-Object| Should Be  $true
        }
    }
}

I got this response:

Describing Create-NewObject
   Context Create-Object
    [-] Runs 574ms
      CommandNotFoundException: Could not find Command & "C:\temp\my.exe"
      at Validate-Command, C:\Program Files\WindowsPowerShell\Modules\Pester\Functions\Mock.ps1: line 801
      at Mock, C:\Program Files\WindowsPowerShell\Modules\Pester\Functions\Mock.ps1: line 168
      at <ScriptBlock>, C:\T\Create-NewObject.tests.ps1: line 13
Tests completed in 574ms
Passed: 0 Failed: 1 Skipped: 0 Pending: 0 Inconclusive: 0

Is there a way to mock this kind of calls without encapsulate them inside a function?

like image 560
XtianGIS Avatar asked Jun 20 '16 15:06

XtianGIS


2 Answers

I found a way to mock the call to this executable files:

function Create-Object
{
   $exp = '& "C:\temp\my.exe"'
   Invoke-Expression -Command $exp
}

And the test with the mock should looks like:

Describe "Create-NewObject" {
    Context "Create-Object" {
        It "Runs" {
            Mock Invoke-Expression {return {$true}} -ParameterFilter {($Command -eq '& "C:\temp\my.exe"')
            Create-Object| Should Be  $true
        }
    }
}
like image 148
XtianGIS Avatar answered Oct 18 '22 17:10

XtianGIS


Yes, unfortunately, as of Pester 4.8.1:

  • you cannot mock external executables by their full paths (e.g, C:\Windows\System32\cmd.exe)
  • you can mock them by file name only (e.g., cmd), but beware that in older Pester versions the mock is only called for invocations that explicitly use the .exe extension (e.g., cmd.exe) - see this (obsolete) GitHub issue

Your own workaround is effective, but it involves Invoke-Expression, which is awkward; Invoke-Expression should generally be avoided

Here's a workaround that uses a helper function, Invoke-External, which wraps the invocation of external programs and, as a function, can itself be mocked, using a -ParameterFilter to filter by executable path:

In your code, define the Invoke-External function and then use it to make your call to c:\temp\my.exe:

# Helper function for invoking an external utility (executable).
# The raison d'être for this function is to allow 
# calls to external executables via their *full paths* to be mocked in Pester.
function Invoke-External {
  param(
    [Parameter(Mandatory=$true)]
    [string] $LiteralPath,
    [Parameter(ValueFromRemainingArguments=$true)]
    $PassThruArgs
  )
  & $LiteralPath $PassThruArgs
}

# Call c:\temp\my.exe via invoke-External
# Note that you may pass arguments to pass the executable as usual (none here):
Invoke-External c:\temp\my.exe

Then, to mock the call to c:\temp\my.exe in your Pester tests:

Mock Invoke-External -ParameterFilter { $LiteralPath -eq 'c:\temp\my.exe' } `
  -MockWith { $true }

Note: If you only have one call to an external executable in your code, you can omit the
-ParameterFilter argument.

like image 36
mklement0 Avatar answered Oct 18 '22 18:10

mklement0