2

I am trying to write Pester tests for my Azure automation runbooks. The runbook script uses the Get-AutomationVariable cmdlet, and I am attempting to mock it via:

mock 'Get-AutomationVariable' {return "localhost:44300"} -ParameterFilter { $name -eq "host"}

which results in the error

CommandNotFoundException: The term 'Get-AutomationVariable' is not recognized as the name of a cmdlet, function, script file, or operable program.

Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

The use of the -ModuleName parameter does not seem appropriate as I am calling the method from a script not a module. Trying to provide a stub module results in the same error being thrown.

. "$here\$sut" "CN=teset, OU=Test" "CN=SubCA02, OU=Test"

Get-Module -Name RunbookMock | Remove-Module
New-Module -Name RunbookMock -ScriptBlock {
  function Get-AutomationVariable {
    [CmdLetBinding()]
    param(
      [string]$Name
    )

    ""
  }
  Export-ModuleMember Get-AutomationVariable
} | Import-Module -Force

describe 'Pin-Certificate' {
  it 'should add an entry to the pinned certificate list'{
    mock 'Get-AutomationVariable' { return "sastoken"} -ParameterFilter {  $Name -eq "StorageSasToken"} -
    mock 'Get-AutomationVariable' {return "localhost:44300"} -ParameterFilter { $name -eq "host"}
  }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tedford
  • 2,842
  • 2
  • 35
  • 45
  • 1
    Your code above works ok for me, with the exception that there is an extra `-` on the end of one of the `mock` lines that looks like a typo. In the past this is the sort of solution i've used to this, although I didn't go as far as declaring a full stub module, but just declared an empty function with the name of the one I want to mock. – Mark Wragg May 04 '18 at 07:28
  • @MarkWragg That is good to know about a stub function, less code is always better. This is my first foray into using pester so quite likely I am doing extra work. – Tedford May 04 '18 at 14:08
  • 2
    Not sure if you've just not provided full code above, but you probably want to use Assert-MockCalled inside your `it` to actually evaluate whether the mock was hit (and you can specify how many times you expected it to be hit too via `-times` i think). – Mark Wragg May 04 '18 at 14:10
  • In case it helps, here's one of my Pester scripts where i've just declared empty functions for a module that isn't present when I test: https://github.com/markwragg/PowerShell-Influx/blob/master/Tests/Send-3ParSystemMetric.Tests.ps1 – Mark Wragg May 04 '18 at 14:11
  • 1
    @MarkWragg The `Assert-MockCalled` is certainly good to keep in mind. In this exact case the output of the method would reflect whether the mocks were called so I can follow an outside-in approach. – Tedford May 04 '18 at 14:32
  • Cool. I think where it makes sense to include it is making sure that the specified cmdlet is running the number of times you expect it to (by using -times and -exactly) so that you were also catching a future scenario where some unexpected repetition was occurring. – Mark Wragg May 04 '18 at 14:34

2 Answers2

3

Per the comments, your code should work. In the past I have just declared an empty function rather than a full module. E.g:

function MyScript {
    Get-FakeFunction
}

Describe 'Some Tests' {

    function Get-Fakefunction {}

    Mock 'Get-Fakefunction' { write-output 'someresult' }

    $Result = MyScript

    it 'should invoke Get-FakeFunction'{
        Assert-MockCalled 'Get-Fakefunction' -Times 1              
    }
    it 'should return someresult'{
        $Result | Should -Be 'someresult'
    }
}
Mark Wragg
  • 22,105
  • 7
  • 39
  • 68
  • This is what i resort to sometimes. The pattern is `function MyFunc {} ; Mock MyFunc { ... }` Is there a better way? – Max Cascone Jun 07 '21 at 20:34
2

The core issues was the sequencing of statements. The script under test was being sourced prior to the declaration of the mock. Reordering the statements such to declare the mocks then source the script resolved the issue and the excellent suggestion from @MarkWragg helped further streamline the code. Its final working state is as follows.

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'

  function Get-AutomationVariable {
    [CmdLetBinding()]
    param([string]$Name)
  }

describe 'Pin-Certificate' {
  it 'should add an entry to the pinned certificate list' {
    mock 'Get-AutomationVariable' -MockWith { "sastoken" } -ParameterFilter {  $Name -eq "StorageSasToken"}
    mock 'Get-AutomationVariable' -MockWith { "localhost:44300" } -ParameterFilter { $Name -eq "host"}
    mock 'Invoke-WebRequest' -MockWith { @{ "StatusCode" = "204"; }   }

    # dot source the sut, invoking the default function
    $result = (. "$here\$sut" "Subject" "Issuer" "Thumbprint")

    $result.Issuer | Should be "Issuer"
    $result.Subject | Should Be "Subject"
    $result.Thumbprint | Should Be "Thumbprint"
  }
Tedford
  • 2,842
  • 2
  • 35
  • 45