pester / Pester

Pester is the ubiquitous test and mock framework for PowerShell.

Home Page:https://pester.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

exchange12rocks opened this issue · comments

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

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:

  • Wrap the type in parentheses to pass the actual type-object
  • Import your classes before running Pester using either using module .\Classes.psm1 or dot-source . .\Classes.ps1. That should make them available in all scopes and session states, including Pester's internal lookup that currently fails.
  • Use $TestO.GetType().Name | Should -Be 'MyTestClass' to assert the type name in this particular scenario.

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.

Thank you @fflaten for this explanation!