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
}
}