pester / Pester

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

BeOfType doesn't see types, loaded from a file with classes, but the "It" function - does #2414

Open exchange12rocks opened 11 months ago

exchange12rocks commented 11 months ago

Checklist

What is the issue?

I have a .ps1 files containing definitions of my classes. I load that file in a BeforeAll block. Later in my tests I can successfully create objects using these classes, but the Should-BeOfType function does not see those types even so the It function - does.

Running the test defined below, I receive the following output:

Starting discovery in 1 files.
Discovery found 1 tests in 880ms.
Running tests.
The type of TestO is: MyTestClass
[-] Test Classes.Object Creation.creates TestClass 717ms (616ms|102ms)
 ArgumentException: Could not find type [MyTestClass]. Make sure that the assembly that contains that type is loaded.
Tests completed in 4.62s
Tests Passed: 0, Failed: 1, Skipped: 0 NotRun: 0

When executing the test step-by-step, I see that my class is indeed not available in the scope of the Should-BeOfType function.

Expected Behavior

Types available in an It block are available to use with the Should -BeOfType command. The test defined below should pass successfully.

Steps To Reproduce

Describe 'Test Classes' {
    BeforeAll {
        . $PSScriptRoot\Classes.ps1
    }

    Describe 'Object Creation' {
        It 'creates TestClass' {
            $TestO = [MyTestClass]::new()
            Write-Host ('The type of TestO is: {0}' -f $TestO.GetType().Name)
            $TestO | Should -BeOfType [MyTestClass]
        }
    }
}

where Classes.ps1 is:

class MyTestClass {
}

Describe your environment

Pester version     : 5.5.0 C:\Users\kf\Documents\PowerShell\Modules\Pester\5.5.0\Pester.psm1
PowerShell version : 7.4.0
OS version         : Microsoft Windows NT 10.0.19045.0

Possible Solution?

No response

fflaten commented 10 months ago

Thanks for the report.

TLDR: Using [MyTestClass] as a parameter value or argument passes the value as a string. The type isn't resolvable by name internally in modules like Pester (see long version for why). If you wrap it in parentheses you'll pass the type-object itself which should work.

Try $TestO | Should -BeOfType ([MyTestClass]) or $TestO.GetType().Name | Should -Be 'MyTestClass'


Long version 🙂

By dot-sourcing Classes.ps1 you only define your classes locally in the current scope. Functions like Should run in a different module-specific session state and scopes which can't see the class in your script scope. The issue is similar to running the script in your session without dot-sourcing.

# Run in child scope similar to your test file. Class is not available when the script is finished -> child scope with class imported is deleted
> & ./Classes.ps1; [MyTestClass]      
InvalidOperation: Unable to find type [MyTestClass].

The typical solution would be to place the class in a module like Classes.psm1 and use e.g. using module .\Classes.psm1 at the top of your test-file to import it. That should've imported it in the global session state, making it available to every module and script. Unfortunately it doesn't. Maybe PowerShell imports the classes in the current scope in the global state which results in the same behavior.

Unfortunately working with classes in PowerShell has lots of weird behavior like this.

A few workarounds:

fflaten commented 10 months ago

Keep open until we've updated docs and function help with an example that passes the type-object properly (with parentheses).

Maybe also be clean up own internal tests, besides https://github.com/pester/Pester/blob/main/tst/functions/assertions/BeOfType.Tests.ps1 which is already good.

exchange12rocks commented 10 months ago

Thank you @fflaten for this explanation!