Skip to content

Select-Object and New-MockObject - Discarded PSObject #2393

Open
@chrisdent-de

Description

@chrisdent-de

Checklist

What is the issue?

When writing unit tests, and mocking complex objects, I want to make use of New-MockObject.

New-MockObject wires up members to the PSObject wrapper, hiding the real properties (or methods). This is a good thing.

New-MockObject avoids Add-Member to achieve this, likely because Add-Member is not regarded as being particularly fast.

This has an unfortunate side-effect if Select-Object is being used in the test subject's code. Select-Object replaces the PSObject wrapper on the selected object, discarding NoteProperty members added via PSObject.Properties.

https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs#L573

Expected Behavior

The following tests describe the expected behaviour. All tests should pass:

$container = New-PesterContainer -ScriptBlock {
    Context 'When using PSObject.Properties.Add' {
        BeforeAll {
            $mockObject = New-MockObject -Type object -Properties @{
                Nested = New-MockObject object -Properties @{
                    Name = 'Value'
                }
            }    
        }

        It 'allows access to the property using the member dereference operator' {
            $mockObject.Nested.Name | Should -Be 'Value'
        }

        It 'allows access to the property using Select-Object' {
            # This test currently fails.
            $mockObject |
                Select-Object -ExpandProperty Nested |
                Select-Object -ExpandProperty Name |
                Should -Be 'Value'
        }
    }

    Context 'When using Add-Member' {
        BeforeAll {
            $addMember = New-MockObject -Type object | Add-Member -PassThru -NotePropertyName 'Nested' -NotePropertyValue (
                New-MockObject object | Add-Member -PassThru -NotePropertyName 'Name' -NotePropertyValue 'Value'
            )
        }

        It 'allows access to the property using the member dereference operator' {
            $addMember.Nested.Name | Should -Be 'Value'
        }

        It 'allows access to the property using Select-Object' {
            # This test currently fails.
            $addMember |
                Select-Object -ExpandProperty Nested |
                Select-Object -ExpandProperty Name |
                Should -Be 'Value'
        }
    }
}
Invoke-Pester -Container $container -Output Detailed

Steps To Reproduce

This problem is exhibited by any object accessed by Select-Object where members have been added using .PSObject.Properties.

$object = [object]::new()
$object.PSObject.Properties.Add(
    [PSNoteProperty]::new(
        'Nested',
        [object]::new()
    )
)
$object.Nested.PSObject.Properties.Add(
    [PSNoteProperty]::new(
        'Name',
        'Value'
    )
)
# This works
$object.Nested.Name
# this doesn't
$object | Select-Object -ExpandProperty Nested | Select-Object -ExpandProperty Name

It is an unfortunate side effect of this problem which affects Pester. However, Pester is the far easier "thing to fix" than PowerShell, so Add-Member may be used to work-around this problem.

Describe your environment

Pester version     : 5.4.0 C:\Development\_modules\Pester\5.4.0\Pester.psm1
PowerShell version : 7.3.6
OS version         : Microsoft Windows NT 10.0.19045.0

Possible Solution?

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions