When writing Pester unit tests for your Powershell code you will probably have a need to mock calls to external functions before too long. This process works as you would expect when Pester can locate a defined function/cmdlet with a matching name. However, if Pester cannot find a definition, it will fail.
This problem will normally surface in a Continuous Integration (CI) environment. For me, it was writing the first suite of tests for the xHyper-V DSC resource module. The Hyper-V cmdlets where present on my authoring machine but were not present on the Appveyor build VM.
Here is an overly simplified Pester test that I’ll use for demonstration purposes:
Describe 'Missing Cmdlet Mocking Example' { InModuleScope 'xVMHyper-V' { It 'Calls Get-VM' { Mock Get-VM -MockWith { } Get-VM -Name 'TestVM'; Assert-MockCalled Get-VM -Scope It; } } }
If we run the test and Pester cannot locate a defined function then it will report an error. Note: if you run this on a machine with the Hyper-V module installed (and you have a VM called ‘TestVM’) then it will pass – but you knew that already ;).
Describing Missing Cmdlet Mocking Example [-] calls Get-VM 474ms Could not find Command Get-VM at line: 600 in C:\Program Files\WindowsPowerShell\Modules\Pester\Functions\Mock.ps1 Tests completed in 474ms
This is easily overcome by defining an empty function within the test file. Note: this will need to be defined within the ‘InModuleScope’ script block if you’re testing a module’s internals.
Describe 'Missing Cmdlet Mocking Example' { InModuleScope 'xVMHyper-V' { Function Get-VM { } It 'Calls Get-VM' { Mock Get-VM -MockWith { } Get-VM -Name 'TestVM'; Assert-MockCalled Get-VM -Scope It; } } }
The test now passes as we would expect. Yay \o/
Describing Missing Cmdlet Mocking Example [+] Calls Get-VM 141ms Tests completed in 141ms Passed: 1 Failed: 0 Skipped: 0 Pending: 0
Now, what you really need to know is that for Pester to enumerate and mock parameter filters, those parameters need to be defined on the stub function. If we were to update the test to check for the passing of a particular –Name parameter like so:
Describe 'Missing Cmdlet Mocking Example' { InModuleScope 'xVMHyper-V' { Function Get-VM { } It 'Calls Get-VM' { Mock Get-VM –ParameterFilter { $Name –eq ‘TestVM } -MockWith { } Get-VM -Name 'TestVM'; Assert-MockCalled Get-VM –ParameterFilter { $Name –eq ‘TestVM’ } -Scope It; } } }
When we run the test it will now fail again.
Describing Missing Cmdlet Mocking Example [-] Calls Get-VM 131ms Expected Get-VM to be called at least 1 times but was called 0 times at line: 518 in C:\Program Files\WindowsPowerShell\Modules\Pester\Functions\Mock.ps1 Tests completed in 131ms Passed: 0 Failed: 1 Skipped: 0 Pending: 0
For Pester to enumerate the dynamic parameters on the function, it needs to have the parameters (only the one’s you’re interested in) defined. This can easily be fixed like so:
Describe 'Missing Cmdlet Mocking Example' { InModuleScope 'xVMHyper-V' { Function Get-VM { param ($Name) } It 'Calls Get-VM' { Mock Get-VM –ParameterFilter { $Name –eq ‘TestVM } -MockWith { } Get-VM -Name 'TestVM'; Assert-MockCalled Get-VM –ParameterFilter { $Name –eq ‘TestVM’ } -Scope It; } } }
The tests will now once again pass successfully!
Describing Missing Cmdlet Mocking Example [+] Calls Get-VM 169ms Tests completed in 169ms Passed: 1 Failed: 0 Skipped: 0 Pending: 0
Hopefully this helps someone and saves some time. It took me a while to work out what was going on as I had the cmdlets available on my development machine but the tests were failing when running in an Appveyor VM. Perhaps I should submit a pull request to get this put into the Pester help documentation?!