Skip to content

Commit

Permalink
Feature Request: ShouldResult property (#2381)
Browse files Browse the repository at this point in the history
* Feature Request: Expected, Actual, and Because as Test Property Fields
Fixes #1993

* Add lingering "Be" because value

* Add missing because argument

* Add a strong type to ShouldResult

* Fix issue with HaveCount having a typed ExpectedValue parameter

* Update src/functions/assertions/Should.ps1

Co-authored-by: Frode Flaten <3436158+fflaten@users.noreply.github.com>

* Refactor to guard clauses to ensure success records do not have additional properties

* CreateShouldErrorRecord to object type

* Add ExpectResult and strong typing to Should -Invoke

* Remove null failuremessage

---------

Co-authored-by: Frode Flaten <3436158+fflaten@users.noreply.github.com>
Co-authored-by: Jakub Jareš <me@jakubjares.com>
  • Loading branch information
3 people committed Apr 16, 2024
1 parent e90392a commit fa6f0b2
Show file tree
Hide file tree
Showing 26 changed files with 398 additions and 165 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Expand Up @@ -22,5 +22,6 @@
"systemId": "https://raw.githubusercontent.com/PowerShell/PowerShell/master/src/Schemas/Types.xsd",
"pattern": "**/*.Types.ps1xml"
}
]
],
"dotnet.defaultSolution": "src/csharp/Pester.sln"
}
18 changes: 11 additions & 7 deletions src/csharp/Pester/Factory.cs
@@ -1,8 +1,8 @@
using System.Management.Automation;
using System.Collections.Generic;
using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using System.Text;

namespace Pester
{
Expand Down Expand Up @@ -33,13 +33,12 @@ public static List<object> CreateCollection()
{
return new List<object>();
}

public static ErrorRecord CreateShouldErrorRecord(string message, string file, string line, string lineText, bool terminating)
public static ErrorRecord CreateShouldErrorRecord(string message, string file, string line, string lineText, bool terminating, object shouldResult = null)
{
return CreateErrorRecord("PesterAssertionFailed", message, file, line, lineText, terminating);
return CreateErrorRecord("PesterAssertionFailed", message, file, line, lineText, terminating, shouldResult);
}

public static ErrorRecord CreateErrorRecord(string errorId, string message, string file, string line, string lineText, bool terminating)
public static ErrorRecord CreateErrorRecord(string errorId, string message, string file, string line, string lineText, bool terminating, object shouldResult = null)
{
var exception = new Exception(message);
// we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system.
Expand All @@ -51,6 +50,11 @@ public static ErrorRecord CreateErrorRecord(string errorId, string message, stri
["LineText"] = lineText,
["Terminating"] = terminating,
};

if (shouldResult is not null)
{
targetObject["ShouldResult"] = shouldResult;
}
return new ErrorRecord(exception, errorId, ErrorCategory.InvalidResult, targetObject);
}

Expand Down
21 changes: 21 additions & 0 deletions src/csharp/Pester/ShouldResult.cs
@@ -0,0 +1,21 @@
namespace Pester
{
public class ShouldResult
{
public bool Succeeded { get; set; }
public string FailureMessage { get; set; }
public ShouldExpectResult ExpectResult { get; set; }
}

public class ShouldExpectResult
{
public string Actual { get; set; }
public string Expected { get; set; }
public string Because { get; set; }

public override string ToString()
{
return $"Expected: {Expected} Actual: {Actual} Because: {Because}";
}
}
}
1 change: 0 additions & 1 deletion src/csharp/Pester/Test.cs
Expand Up @@ -34,7 +34,6 @@ public Test()
public object Data { get; set; }
public string ExpandedName { get; set; }
public string ExpandedPath { get; set; }

public string Result { get; set; }
public List<object> ErrorRecord { get; set; }
public object StandardOutput { get; set; }
Expand Down
73 changes: 56 additions & 17 deletions src/functions/Mock.ps1
Expand Up @@ -300,6 +300,7 @@ function Create-MockHook ($contextInfo, $InvokeMockCallback) {

function Should-InvokeVerifiableInternal {
[CmdletBinding()]
[OutputType([Pester.ShouldResult])]
param(
[Parameter(Mandatory)]
$Behaviors,
Expand All @@ -315,31 +316,45 @@ function Should-InvokeVerifiableInternal {
}

if ($filteredBehaviors.Count -gt 0) {
if ($Negate) { $message = "$([System.Environment]::NewLine)Expected no verifiable mocks to be called,$(Format-Because $Because) but these were:" }
else { $message = "$([System.Environment]::NewLine)Expected all verifiable mocks to be called,$(Format-Because $Because) but these were not:" }

[string]$filteredBehaviorMessage = ''
foreach ($b in $filteredBehaviors) {
$message += "$([System.Environment]::NewLine) Command $($b.CommandName) "
$filteredBehaviorMessage += "$([System.Environment]::NewLine) Command $($b.CommandName) "
if ($b.ModuleName) {
$message += "from inside module $($b.ModuleName) "
$filteredBehaviorMessage += "from inside module $($b.ModuleName) "
}
if ($null -ne $b.Filter) { $message += "with { $($b.Filter.ToString().Trim()) }" }
if ($null -ne $b.Filter) { $filteredBehaviorMessage += "with { $($b.Filter.ToString().Trim()) }" }
}

if ($Negate) {
$message = "$([System.Environment]::NewLine)Expected no verifiable mocks to be called,$(Format-Because $Because) but these were:$filteredBehaviorMessage"
$ExpectedValue = 'No verifiable mocks to be called'
$ActualValue = "These mocks were called:$filteredBehaviorMessage"
}
else {
$message = "$([System.Environment]::NewLine)Expected all verifiable mocks to be called,$(Format-Because $Because) but these were not:$filteredBehaviorMessage"
$ExpectedValue = 'All verifiable mocks to be called'
$ActualValue = "These mocks were not called:$filteredBehaviorMessage"
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = $message
ExpectResult = @{
Expected = $ExpectedValue
Actual = $ActualValue
Because = Format-Because $Because
}
}
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $true
FailureMessage = $null
}
}

function Should-InvokeInternal {
[CmdletBinding(DefaultParameterSetName = 'ParameterFilter')]
[OutputType([Pester.ShouldResult])]
param(
[Parameter(Mandatory = $true)]
[hashtable] $ContextInfo,
Expand Down Expand Up @@ -452,43 +467,67 @@ function Should-InvokeInternal {

if ($Negate) {
# Negative checks
if ($matchingCalls.Count -eq $Times -and ($Exactly -or !$PSBoundParameters.ContainsKey("Times"))) {
return [PSCustomObject] @{
if ($matchingCalls.Count -eq $Times -and ($Exactly -or !$PSBoundParameters.ContainsKey('Times'))) {
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected ${commandName}${moduleMessage} not to be called exactly $Times times,$(Format-Because $Because) but it was"
ExpectResult = [Pester.ShouldExpectResult]@{
Expected = "${commandName}${moduleMessage} not to be called exactly $Times times"
Actual = "${commandName}${moduleMessage} was called $($matchingCalls.count) times"
Because = Format-Because $Because
}
}
}
elseif ($matchingCalls.Count -ge $Times -and !$Exactly) {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected ${commandName}${moduleMessage} to be called less than $Times times,$(Format-Because $Because) but was called $($matchingCalls.Count) times"
ExpectResult = [Pester.ShouldExpectResult]@{
Expected = "${commandName}${moduleMessage} to be called less than $Times times"
Actual = "${commandName}${moduleMessage} was called $($matchingCalls.count) times"
Because = Format-Because $Because
}
}
}
}
else {
if ($matchingCalls.Count -ne $Times -and ($Exactly -or ($Times -eq 0))) {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected ${commandName}${moduleMessage} to be called $Times times exactly,$(Format-Because $Because) but was called $($matchingCalls.Count) times"
ExpectResult = [Pester.ShouldExpectResult]@{
Expected = "${commandName}${moduleMessage} to be called $Times times exactly"
Actual = "${commandName}${moduleMessage} was called $($matchingCalls.count) times"
Because = Format-Because $Because
}
}
}
elseif ($matchingCalls.Count -lt $Times) {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected ${commandName}${moduleMessage} to be called at least $Times times,$(Format-Because $Because) but was called $($matchingCalls.Count) times"
ExpectResult = [Pester.ShouldExpectResult]@{
Expected = "${commandName}${moduleMessage} to be called at least $Times times"
Actual = "${commandName}${moduleMessage} was called $($matchingCalls.count) times"
Because = Format-Because $Because
}
}
}
elseif ($filterIsExclusive -and $nonMatchingCalls.Count -gt 0) {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected ${commandName}${moduleMessage} to only be called with with parameters matching the specified filter,$(Format-Because $Because) but $($nonMatchingCalls.Count) non-matching calls were made"
ExpectResult = [Pester.ShouldExpectResult]@{
Expected = "${commandName}${moduleMessage} to only be called with with parameters matching the specified filter"
Actual = "${commandName}${moduleMessage} was called $($nonMatchingCalls.Count) times with non-matching parameters"
Because = Format-Because $Because
}
}
}
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $true
FailureMessage = $null
}
}

Expand Down
48 changes: 29 additions & 19 deletions src/functions/assertions/Be.ps1
Expand Up @@ -26,18 +26,23 @@ function Should-Be ($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Be

$failureMessage = ''

if (-not $succeeded) {
if ($Negate) {
$failureMessage = NotShouldBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue -Because $Because
}
else {
$failureMessage = ShouldBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue -Because $Because
}
if ($true -eq $succeeded) { return [Pester.ShouldResult]@{Succeeded = $succeeded } }

if ($Negate) {
$failureMessage = NotShouldBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue -Because $Because
}
else {
$failureMessage = ShouldBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue -Because $Because
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $succeeded
FailureMessage = $failureMessage
ExpectResult = @{
Actual = Format-Nicely $ActualValue
Expected = Format-Nicely $ExpectedValue
Because = $Because
}
}
}

Expand All @@ -64,9 +69,9 @@ function NotShouldBeFailureMessage($ActualValue, $ExpectedValue, $Because) {
}

& $script:SafeCommands['Add-ShouldOperator'] -Name Be `
-InternalName Should-Be `
-Test ${function:Should-Be} `
-Alias 'EQ' `
-InternalName Should-Be `
-Test ${function:Should-Be} `
-Alias 'EQ' `
-SupportsArrayInput

Set-ShouldOperatorHelpMessage -OperatorName Be `
Expand Down Expand Up @@ -99,18 +104,23 @@ function Should-BeExactly($ActualValue, $ExpectedValue, $Because) {

$failureMessage = ''

if (-not $succeeded) {
if ($Negate) {
$failureMessage = NotShouldBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Because $Because
}
else {
$failureMessage = ShouldBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Because $Because
}
if ($true -eq $succeeded) { return [Pester.ShouldResult]@{Succeeded = $succeeded } }

if ($Negate) {
$failureMessage = NotShouldBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Because $Because
}
else {
$failureMessage = ShouldBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Because $Because
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $succeeded
FailureMessage = $failureMessage
ExpectResult = @{
Actual = Format-Nicely $ActualValue
Expected = Format-Nicely $ExpectedValue
Because = $Because
}
}
}

Expand Down
18 changes: 14 additions & 4 deletions src/functions/assertions/BeGreaterThan.ps1
Expand Up @@ -14,13 +14,18 @@
}

if ($ActualValue -le $ExpectedValue) {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected the actual value to be greater than $(Format-Nicely $ExpectedValue),$(Format-Because $Because) but got $(Format-Nicely $ActualValue)."
ExpectResult = @{
Actual = Format-Nicely $ActualValue
Expected = Format-Nicely $ExpectedValue
Because = $Because
}
}
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $true
}
}
Expand All @@ -47,13 +52,18 @@ function Should-BeLessOrEqual($ActualValue, $ExpectedValue, [switch] $Negate, [s
}

if ($ActualValue -gt $ExpectedValue) {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected the actual value to be less than or equal to $(Format-Nicely $ExpectedValue),$(Format-Because $Because) but got $(Format-Nicely $ActualValue)."
ExpectResult = @{
Actual = Format-Nicely $ActualValue
Expected = Format-Nicely $ExpectedValue
Because = $Because
}
}
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $true
}
}
Expand Down
16 changes: 13 additions & 3 deletions src/functions/assertions/BeIn.ps1
Expand Up @@ -16,20 +16,30 @@

if (-not $succeeded) {
if ($Negate) {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected collection $(Format-Nicely $ExpectedValue) to not contain $(Format-Nicely $ActualValue),$(Format-Because $Because) but it was found."
ExpectResult = @{
Actual = Format-Nicely $ActualValue
Expected = Format-Nicely $ExpectedValue
Because = $Because
}
}
}
else {
return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $false
FailureMessage = "Expected collection $(Format-Nicely $ExpectedValue) to contain $(Format-Nicely $ActualValue),$(Format-Because $Because) but it was not found."
ExpectResult = @{
Actual = Format-Nicely $ActualValue
Expected = Format-Nicely $ExpectedValue
Because = $Because
}
}
}
}

return [PSCustomObject] @{
return [Pester.ShouldResult] @{
Succeeded = $true
}
}
Expand Down

0 comments on commit fa6f0b2

Please sign in to comment.