pester / Pester

Pester is the ubiquitous test and mock framework for PowerShell.
https://pester.dev/
Other
3.08k stars 470 forks source link

Cant use New-MockObject with custom class from within a module #2564

Open dglambert opened 1 week ago

dglambert commented 1 week ago

Checklist

What is the issue?

Synopsis: When trying to calling New-MockObject -Type 'Config' where Config is a class defined in another psm1 file, I get the following error PSInvalidCastException: Cannot convert the "Config" value of type "System.String" to type "System.Type"

Using Powershell 5.1 with Pester V5.6.1

I have put together the following simple sample to demonstrate the problem

All files are in the same directory

Config.psm1

class Config 
{
    [ValidateLength(1, 1000)]
    [string] $A

    [ValidateLength(1, 1000)]
    [string] $B    

     Config( [string] $pathToJson) 
     {
        try 
        {
            $jsonContent = Get-Content -Path $pathToJson -Raw | ConvertFrom-Json

            $this.A = $jsonContent.A
            $this.B = $jsonContent.B
        } 
        catch 
        {
            Write-Warning $_.Exception.Message
            Write-Warning "Check json file for missing value(s)"
            throw "Error loading configuration from file: $($_.Exception.Message)"
        }
    }
}

Export-ModuleMember -Function Config 

Client.psm1

using module .\Config.psm1

class Client
{
    [Config] $config

    Client([Config] $config) 
    {
        $this.config = $config
    }

    [string] JoinAandB()
    {        
        return $this.config.A + $this.config.B
    }
}

Export-ModuleMember -Function Client

Client.Tests.psm1

using module .\Config.psm1
using module .\Client.psm1

Describe "SomeClassThatUsesAzureDevOpsAgentConfig" {    

    It "JoinAandB__A_and_B_Are_Populated__ReturnsAB" {
        # Arrange
        $mockConfig = New-MockObject -Type  'Config' -Properties {A = "foo" B = "Bar"}
        $systemUnderTest = New-Object Client($mockConfig)
        $expectedResult = "AB"

        # Act
        $actualResults = $objectUnderTest.JoinAandB()

        # Assert
        $actualResults | Should -Be $expectedResult
    }
}

I'm not including the JSON since its not relevant, as I am trying to mock the config class, so it should not be needed.

The Error message I get when I run Invoke-Pester .\Client.Tests.ps1

Starting discovery in 1 files.
Discovery found 1 tests in 16ms.
Running tests.
[-] SomeClassThatUsesAzureDevOpsAgentConfig.JoinAandB__A_and_B_Are_Populated__ReturnsAB 5ms (5ms|1ms)
 PSInvalidCastException: Cannot convert the "Config" value of type "System.String" to type "System.Type".
 ArgumentTransformationMetadataException: Cannot convert the "Config" value of type "System.String" to type "System.Type".
 ParameterBindingArgumentTransformationException: Cannot process argument transformation on parameter 'Type'. Cannot convert the "Config" value of type "System.String" to type "System.Type".
 at <ScriptBlock>, C:\Users\dgleason\source\repos\DevOpsTeam\IaC\installs\azure_devops_agent_configuration\Client.Tests.ps1:9
Tests completed in 77ms
Tests Passed: 0, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0

The weird thing is, when I was developing my unit tests, they were all passing, it wasn't until some point till I made it to my 6th or 7th test that it started failing. This makes me think the way I had my module loaded in my IDE (VS Code) bypassed the issue.

Expected Behavior

Calling New-MockObject -Type 'Config' should return my mocked class

Steps To Reproduce

Executing Invoke-Pester .\Client.Tests.ps1 from current working directory in IDE terminal after closing and reopenning, or via new Powershell window to avoid any caching of classes/modules.

Describe your environment

Pester version : 5.6.1 C:\Program Files\WindowsPowerShell\Modules\Pester\5.6.1\Pester.psm1 PowerShell version : 5.1.19041.4648 OS version : Microsoft Windows NT 10.0.19045.0

Possible Solution?

No response

dglambert commented 5 days ago

Closing the loop on this, I was able to make this work by changing -Type 'Config' to -Type ([Config]) - credit for the answer goes to @mklement0 at https://stackoverflow.com/questions/78979730/how-to-use-pester-new-mockobject-with-class-from-within-a-module

However, I'm not sure why this works, I think the documentation at the very least should be updated.

fflaten commented 5 days ago

Glad you figured it out. Likely same explanation as this.

Let's leave this open to update matching docs as you've suggested. 🙂

If you or someone would like to contribute a PR, I suggest adding a note about using parantheses around type-values in -Type parameter help + a fixed example in https://github.com/pester/Pester/blob/main/src/functions/New-MockObject.ps1.