Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: ShouldResult property #2381

Merged
merged 11 commits into from
Apr 16, 2024
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading