Search code examples
powershellpester

Problem using PowerShell Class contained in module when running Pester test for a different module that utilizes the Class module


I wrote the following MyTypes module with a simple Class:

class Person {
    [string] $Name
 
    Person([string] $name ) {
        $this.Name = $name
    }
}

Then I defined the following MyModule module to use it:

using module "C:\temp\StackOverflow\PSModules\MyTypes\MyTypes.psm1"
Import-Module "C:\temp\StackOverflow\PSModules\MyTypes\MyTypes.psm1" -Force

function MyFunction {
    param (
        [Parameter(Mandatory = $true)]
        [Person] $person
    )
    Write-Output "Hello $($person.Name)"

}

# Export the function
Export-ModuleMember -Function MyFunction 

And finally wrote a Pester test that is failing as it cannot seem to utilize the reference to the Class define in the MyTypes module

using module "C:\temp\StackOverflow\PSModules\MyTypes\MyTypes.psm1"

BeforeAll {
    Import-Module "C:\temp\StackOverflow\PSModules\MyTypes\MyTypes.psm1" -Force
    Import-Module "C:\temp\StackOverflow\PSModules\MyModule\MyModule.psm1" -Force
}
        
Describe 'MyFunction' {

        Mock Write-Output {}
        $person = [Person]::new("John Doe")

        It 'should call my function' {
            MyFunction -Person $person 
            Assert-MockCalled -CommandName Write-Output -Exactly 1 -Scope It 
        } 

}

I receive an error "Cannot bind argument to parameter 'person' because it is null" when running the Pester test. In my real code, I am actually receiving an error that the Pester cannot resolve the type. I'd like to use a module like MyTypes to keep the Classes organized and referenced in my code which works nicely for the real code, but giving me fits with the Pester tests. Does anyone have insight on this problem or the solution?

I am running Pester 5.7.1 and PowerShell 7.5.0


Solution

  • Very similar issue as the one described in Pester Mock a command within a PowerShell class function, you're missing -ModuleName in both, your Mock and your Assert-MockCalled statements. In addition, the Mock statement should be inside a BeforeAll block. MyModule in this example should correspond with the actual module name.

    using module ...\MyTypes.psm1
    Import-Module ...\MyTypes.psm1, ...\MyModule.psm1 -Force
    
    Describe 'MyFunction' {
        BeforeAll {
            $person = [Person]::new('John Doe')
            $mockSplat = @{ CommandName = 'Write-Output'; ModuleName = 'MyModule' }
            Mock @mockSplat -MockWith { }
        }
    
        It 'should call my function' {
            MyFunction -Person $person
            Assert-MockCalled @mockSplat -Exactly 1 -Scope It
        }
    }
    

    Same as suggested in the linked answer, using InModuleScope is another option that wouldn't require you to add the -ModuleName parameter in the mock calls:

    InModuleScope 'MyModule' {
        Describe 'MyClass' {
            BeforeAll {
                $person = [Person]::new('John Doe')
                Mock -CommandName Write-Output -MockWith { }
            }
    
            It 'should call my function' {
                MyFunction -Person $person
                Assert-MockCalled -CommandName Write-Output -Exactly 1 -Scope It
            }
        }
    }