简体   繁体   中英

Mock a function within a .ps1 script

I have a PowerShell .ps1 file which contains functions at the top of the script followed by different commands calling these functions. I am using Pester to unit test my script file.

How do I mock a function that is within my PowerShell .ps1 script?

I have tried mocking the function, but I get an error saying "could not find command".

I have also tried adding an empty "dummy" function in the describe block. This doesn't give me the above error, but it is not mocking the function within the script correctly.

I have two files. One to hold the tests and another that holds the functions and calls to the functions. Below are two examples:

File1.ps1

Function Backup-Directory([switch]$IsError)
{
    If($IsError)
    {
        Write-Error "Error"
        Exit 1
    }
}

Backup-Directory $true

File2.Tests.ps1

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

Describe "File1" {

    Context "When the Back-Directory outputs an error." {

        # Arrange
        Mock Back-Directory { }
        Mock Write-Error

        # Act
        & $productionFile
        $hasSucceeded = $?

        # Assert
        It "Calls Backup-Directory" {
            Assert-MockCalled Backup-Directory -Exactly 1 -ParameterFilter {
                $IsError -eq $true
            }
        }

        It "Writes error message." {
            Assert-MockCalled Write-Error -Exactly 1 -ParameterFilter {
                 $Message -eq "Error"
            }
        }

        It "Exits with an error." {
            $hasSucceeded | Should be $false
        }
    }
}

I don't think this is possible. At least with your current implementation. I asked this same question a while back... Pester Issue 414

BUT you could split out that inner function into another script file in the same directory allowing you to unit test and mock it. You would just have to dot source the function in your main script file to be able to use it:

Main-Function.ps1:

# Main script
function Main-Function {
    # if debugging, set moduleRoot to current directory
    if ($MyInvocation.MyCommand.Path) {
        $moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path
    }else {
        $moduleRoot = $PWD.Path
    }

    # dot source the inner function
    . "$moduleRoot\Inner-Function.ps1"

    Write-Output "This is the main script. Calling the inner function"

    Inner-Function

    Write-Output "Back in the main script"
}

Inner-Function.ps1:

function Inner-Function {
    Write-Output "This is the inner function"
}

Main-Function.Tests.ps1:

$moduleRoot =  Split-Path -Parent $MyInvocation.MyCommand.Path 

# Load Testing Function
. "$moduleRoot\Main-Function.ps1"

# Load Supporting Functions
. "$moduleRoot\Inner-Function.ps1"

Describe 'Main Script' {
    Mock Inner-Function { return "This is a mocked function" }

    It 'uses the mocked function' {
        (Main-Function -match "This is a mocked function") | Should Be $true
    }
}

This is a really nice approach because we can unit test the inner function and as the logic grows, adding tests to it is very easy (and can be done in isolation from the rest of the scripts/functions).

Inner-Functions.Tests.ps1:

$moduleRoot =  Split-Path -Parent $MyInvocation.MyCommand.Path 

# Load Testing Function
. "$moduleRoot\Inner-Function.ps1"

Describe 'Inner Function' {
    It 'outputs some text' {
        Inner-Function | Should Be "This is the inner function"
    }
}

There are two main points here...

Finding the dependent function location regardless of your current execution directory...

if ($MyInvocation.MyCommand.Path) {
        $moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path
    }else {
        $moduleRoot = $PWD.Path
    }

AND

Dot Sourcing depending functions in the main function as well as the unit test files... . "$moduleRoot\\Inner-Function.ps1" . "$moduleRoot\\Inner-Function.ps1"

Split-Path -Path $MyInvocation.MyCommand.Path is $null in a user session, but will NOT be $null when invoked from within an execution context. Meaning, you could be in C:\\users\\nhudacin and still load this script/module correctly. Otherwise you would have to always execute this script/module from within the same directory as where it's located without using $MyInvoation variable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM