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

Should does NOT assert correctly for certain calls when calling it in different ways

TofuBug opened this issue · comments

Checklist

What is the issue?

Preface

PowerShell has the ability to preload Cmdlet parameters in Hashtables used to Splat the eventual Cmdlet Call

Pester is made up of Cmdlet so this tenant should hold true

So for instance

'10 | Should -Be 10

Could be splatted like

$Should =
    @{
        ActualValue =
            10
        ExpectedValue =
            10
        Be =
            $true
    }
Should @Should

And this example works just fine

However something like in the steps to reproduce does not

Expected Behavior

Yes Splatting might not be the traditional way to call Pester but a lot of places have coding style guides that heavily prioritize splatting and Pester's style flies in the face of that

I'm not here to argue the merrits of one coding style over another simply that

Regardless of the means to call the Cmdlet the result should be the same

The fact the error states it expected X and got X is a bit humorous to me

Steps To Reproduce

$Should =
	@{
		ActualValue =
			@(1,2,3)
		ExpectedValue =
			@(1,2,3)
		Be =
			$true
	}

Works with

$Should.ActualValue | Should -Be $Should.ExpectedValue

but NOT

Should -ActualValue $Should.ActualValue -Be -ExpectedValue $Should.ExpectedValue

or

Should @should

The last two both happily report

Expected @(1, 2, 3), but got @(1, 2, 3).
At C:\Program Files (x86)\WindowsPowerShell\Modules\Pester\5.4.0\Pester.psm1:8130 char:13
+             throw $errorRecord
+             ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidResult: (System.Collecti...,System.Object]:Dictionary`2) [], Exceptio
   n
    + FullyQualifiedErrorId : PesterAssertionFailed

Describe your environment

Windows 10.
Windows PowerShell, and PowerShell

Possible Solution?

For the above example with any complicated object the solution is to have a Compare-Object call as the ActualValue and BeNullOrEmpty as the assertion so

$CompareObject =
	@{
		ReferenceObject =
			@(1,2,3)
		DifferenceObject =
			@(1,2,3)
	}
$Should =
	@{
		ActualValue =
			Compare-Object @CompareObject
		BeNullOrEmpty =
			$true
	}
Should @Should

Will work and you can add Property to the hashtable for complicated object comparisions

Hi. Thanks for the report! This really boils down to:

    It 'works' {
        @(1,2,3) | Should -Be -ExpectedValue @(1,2,3)
    }

    It 'fails' {
        Should -ActualValue @(1,2,3) -Be -ExpectedValue @(1,2,3)
    }

The issue is that when using the pipeline, PowerShell unrolls the array. To enable array-comparisons we overcome this by adding $ActualValue to a new array, but when providing an array using -ActualValue it actually becomes @(@(1,2,3)) at the moment. As a workaround, you could wrap your expected value in a similar outer array using the comma operator:

    It 'workaround' {
        Should -ActualValue @(1,2,3) -Be -ExpectedValue (,@(1,2,3))
    }

   It 'workaround splatting' {
        $ShouldParams =
        @{
            ActualValue   =
            @(1,2,3)
            ExpectedValue =
            ,@(1,2,3)  # notice the , to create a nested array
            Be            =
            $true
        }
        Should @ShouldParams 
    }

The issue is that when using the pipeline, PowerShell unrolls the array. To enable array-comparisons we overcome this by adding $ActualValue to a new array, but when providing an array using -ActualValue it actually becomes @(@(1,2,3)) at the moment. As a workaround, you could wrap your expected value in a similar outer array using the comma operator:

I get the explanation but why do we have to remember a different way of putting our expected value in when you should be able to check if ActualValue came from the pipeline and ONLY wrap it in an array in that case

So instead of

begin {
    $inputArray = [System.Collections.Generic.List[PSObject]]@()
}

process {
    $inputArray.Add($ActualValue)
}

have

    begin {
        $inputArray = $ActualValue        
        if ($PSCmdlet.MyInvocation.ExpectingInput) {
            $inputArray = [System.Collections.Generic.List[PSObject]]@()
        }
    }

    process {
        if ($PSCmdlet.MyInvocation.ExpectingInput) {
            $inputArray.Add($ActualValue)
        }
    }

You might not have noticed that I called it a workaround, didn't close the issue and linked a related PR (possible breaking change so no promises yet). 🙂

You might not have noticed that I called it a workaround, didn't close the issue and linked a related PR (possible breaking change so no promises yet). 🙂

I saw after I responded LOL

Seems you were going down the same general thought process I was.

I appreciate the attempt at a merge