Running Pester test from within a module fails to test a private function

Can anyone please tell me what I am doing wrong?

I’m creating my own pwsh module with some functions I find handy.
One of those functions is for generating unit test and code coverage HTML reports (New-UnitTestReport).

While developing function New-UnitTestReport I am testing it using the Pester tests I’ve created for module LazyGuy.
Module LazyGuy requires private function Invoke-Butler, the module does not function without it. Hence, I have created helper function Assert-HelperFunction to validate if private function Invoke-Butler is present when any of the public functions of the module is called. Each public functions calls Assert-HelperFunction.

The fictitious module:

C:\GIT\FANCYSTUFF\MODULES
└─── LazyGuy
    │   LazyGuy.psd1
    │   LazyGuy.psm1
    │
    ├───private
    │       Assert-HelperFunction.ps1
    │       Invoke-Butler.ps1
    │
    └───public
            Clear-MyDesk.ps1
            Get-Coffee.ps1
            New-Beer.ps1

Assert-HelperFunction:

function Assert-HelperFunction {
    <#
    .SYNOPSIS
        Throw error if a helper function is not available.

    .DESCRIPTION
        Assert-HelperFunction asserts if the helper function is available during execution.
    #>
    [string]$ModuleName = 'LazyGuy'
    [string[]]$FunctionNames = 'Invoke-Butler'
    $Module = Get-Module -Name $ModuleName

    if ([string]::IsNullOrWhiteSpace($Module)) {
        throw "Missing module '$ModuleName'"
    }

    $ModuleFunctions = $Module.Invoke( { Get-Command -Module $ModuleName } )

    foreach ($FunctionName in $FunctionNames) {
        $Dependency = $ModuleFunctions | Where-Object -Property Name -EQ $FunctionName

        if ([string]::IsNullOrWhiteSpace($Dependency)) {
            [string[]]$MissingFunction += $FunctionName
        }
    }
    if (-not [string]::IsNullOrWhiteSpace($MissingFunction)) {
        throw "Missing dependent function '$($MissingFunction -join ' & ')'"
    }
}

Function Assert-HelperFunction has its own Pester test.

Assert-HelperFunction.tests.ps1:

$ModuleName = 'LazyGuy'
$ModulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\modules\$ModuleName"
Remove-Module $ModuleName -ErrorAction SilentlyContinue
Import-Module -Name $ModulePath

Describe 'Function Assert-HelperFunction' {
    Context 'asserting function' {
        It 'should call throw default error Execution failed' {
            InModuleScope 'LazyGuy' {
                Set-Variable -Name MissingFunction -Value mock -Scope Script

                { Assert-HelperFunction } | Should -Throw "Missing dependent function 'mock'"
            }
        }
    }
}

This test runs successfully when running command ‘Invoke-Pester -Path C:\git\FancyStyff\tests\Assert-HelperFunction.tests.ps1’

However, when I run this test via module function New-UnitTestReport I get error:

Running tests from ‘C:\git\FancyStuff\tests\Assert-HelperFunction.tests.ps1’
Describing Function Assert-HelperFunction
Context asserting function
[-] should call throw default error Execution failed 26ms (19ms|7ms)
Expected an exception, with message ‘Missing dependent function ‘mock’’ to be thrown, but the message was ‘Missing module ‘LazyGuy’’. from C:\git\LazyGuy\modules\FancyStuff\private\Assert-HelperFunction.ps1:14 char:9
+ throw “Missing module ‘$ModuleName’”
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
at { Assert-HelperFunction } | Should -Throw “Missing dependent function ‘mock’”, C:\git\FancyStuff\tests\Assert-HelperFunction.tests.ps1:12
at , C:\git\FancyStuff\tests\Assert-HelperFunction.tests.ps1:12

When I convert the function to a script this error is not thrown!

Module function New-UnitTestReport (it’s work in progress):

function New-UnitTestReport {
    <#
    .SYNOPSIS

    .DESCRIPTION

    .EXAMPLE
    $Param = @{
        Path                = 'C:\git\FancyStuff\tests'\
        ScriptPath          = 'C:\git\FancyStuff\modules\LazyGuy\private\*.ps1', 'C:\git\FancyStuff\modules\LazyGuy\public\*.ps1'
        ReportUnitPath      = 'C:\ReportUnit\ReportUnit.exe'
        ReportGeneratorPath = 'C:\ReportGenerator\ReportGenerator.exe'
        ReportType          = 'HtmlInline_AzurePipelines', 'HtmlInline_AzurePipelines_Dark'
        ReportTitle         = 'Nice Stuff!!'
        ShowReport          = $true
    }
    New-UnitTestReport @Param

    This command will . . .
    #>
    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Directories to be searched for tests, paths directly to test files, or combination of both.')]
        [string]$Path,
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Directorie where the scripts are located for code coverage.')]
        [array]$ScriptPath,
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Full path of ReportUnit.exe. (e.g. "C:\ReportUnit\ReportUnit.exe")')]
        [string]$ReportUnitPath,
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Full path of ReportGenerator.exe. (e.g. "C:\ReportGenerator\ReportGenerator.exe")')]
        [string]$ReportGeneratorPath,
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Code coverage output format and scope.')]
        [ValidateSet('Badges', 'Clover', 'Cobertura', 'CsvSummary', 'Html', 'HtmlChart',
            'HtmlInline', 'HtmlInline_AzurePipelines', 'HtmlInline_AzurePipelines_Dark',
            'HtmlSummary', 'JsonSummary', 'Latex', 'LatexSummary', 'lcov', 'MHtml',
            'PngChart', 'SonarQube', 'TeamCitySummary', 'TextSummary', 'Xml', 'XmlSummary')]
        [System.Collections.ArrayList]$ReportType = 'Html',
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Title of the code coverage report.')]
        [string]$ReportTitle = 'Code Coverage',
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Open HTML report.')]
        [switch]$ShowReport
    )
    #Requires -Modules @{ ModuleName='Pester'; ModuleVersion='5.2.2' }

    $ErrorPrefernce = $ErrorActionPreference
    $ErrorActionPreference = 'SilentlyContinue'
    $AssertReportGenerator = & $ReportGeneratorPath /?
    $ErrorActionPreference = $ErrorPrefernce

    if ([string]::IsNullOrWhiteSpace($AssertReportGenerator)) {
        throw "`nUnable to find ReportGenerator.exe. `nPlease make sure ReportGenerator.exe is installed. `nhttps://danielpalme.github.io/ReportGenerator/"
    }

    # Run Pester test

    ### ToDo: Specify report output directory path. Create path if not exist including dir for test results and code coverage
    ### E.g. c:\testResults\unitTest\ & c:\testResults\codeCoverage\

    $CodeCoverageOutputPath = "$Path/coverage.xml"
    $TestResultOutputPath = "$Path/testResults.xml"

    $configuration = New-PesterConfiguration
    $configuration.Run.Path = $Path
    $configuration.Run.Exit = $true
    $configuration.Should.ErrorAction = 'Continue'
    $configuration.CodeCoverage.Enabled = $true
    $configuration.CodeCoverage.OutputFormat = 'JaCoCo'
    $configuration.CodeCoverage.OutputPath = $CodeCoverageOutputPath
    $configuration.CodeCoverage.Path = $ScriptPath
    $configuration.TestResult.Enabled = $true
    $configuration.TestResult.OutputFormat = 'NUnitXml'
    $configuration.TestResult.OutputPath = $TestResultOutputPath
    $configuration.Output.Verbosity = 'Detailed'

    try {
        Invoke-Pester -Configuration $configuration

        # Generating unit tests report
        & $ReportUnitPath $TestResultOutputPath | Out-String | Out-Null ### ToDo: Add report destinatino path

        # Generating code coverage report
        if ($ReportType.Contains('HtmlInline_AzurePipelines') -and $ReportType.Contains('HtmlInline_AzurePipelines_Dark')) {
            Write-Warning "You specified report type 'HtmlInline_AzurePipelines' and 'HtmlInline_AzurePipelines_Dark'. `nOnly one of these report types can be created at one time. `nRemoving report type 'HtmlInline_AzurePipelines_Dark'`n"
            $ReportType.Remove('HtmlInline_AzurePipelines_Dark')
        }

        [string]$CoverageReportDir = 'coveragereport'
        [string]$ReportType = Join-String -InputObject $ReportType -Separator ';'
        [string]$SourceDirs = $ScriptPath.TrimEnd('*.ps1') | Join-String -Separator ';'

        $CodeCoverageResult = & $ReportGeneratorPath -reports:$CodeCoverageOutputPath -targetdir:$CoverageReportDir -sourcedirs:$SourceDirs -reporttypes:$ReportType -title:$ReportTitle | Out-String
        # $Result

        if ($ShowReport.IsPresent) {
            $DirSeparator = [IO.Path]::DirectorySeparatorChar

            #&

            foreach ($Entry in $CodeCoverageResult.Split(':')) {
                if ($Entry -match 'Writing report file') {
                    [string]$Report = $Entry.Split("'")[1].Split("$DirSeparator")[-1]
                    Write-Host "Operning Report '$CoverageReportDir$DirSeparator$Report'" -ForegroundColor Magenta

                    & $CoverageReportDir$DirSeparator$Report
                }
            }
        }
    }
    finally {
        Remove-Item -LiteralPath $CodeCoverageOutputPath -Force -ErrorAction SilentlyContinue
        Remove-Item -LiteralPath $TestResultOutputPath -Force -ErrorAction SilentlyContinue
    }
}

looks like a execution path issue. Try giving full path of the .psd1 file of the module instead of name.

Hi @kvprasoon,

Thank you for your feedback.
I did give your suggestion an attempt. It did not solve the issue.

The Pester test itself runs without any problem when I run is directly via Invoke-Pester. However, when it is run via function New-UnitTestReport (imported via its own module) it fails.

I’ve added a Write-Host "$(Get-Module | Out-String)" to show which modules are loaded.
Running Invoke-Pester the module of function Assert-HelperFunction is shown.
When running using function New-UnitTestReport it isn’t shown.

The strangest thing is that when I copy and past the function code (New-UnitTestReport) directly in the terminal it works like a charm.

Thnx to @nohwnd for solving this issue :call_me_hand:t3:
The cause, and solution, is described in GitHub
Running Pester test from within a module fails to test a private function. · Issue #2003 · pester/Pester (github.com)

1 Like