diff --git a/build.ps1 b/build.ps1 index 293c69a6d..2117fe89a 100644 --- a/build.ps1 +++ b/build.ps1 @@ -277,9 +277,12 @@ $script = @( "$PSScriptRoot/src/Pester.Runtime.ps1" "$PSScriptRoot/src/TypeClass.ps1" "$PSScriptRoot/src/Format.ps1" + # TODO: the original version of Format2 from assert, because it does not format strings and other stuff in Pester specific way. I've used this regex (Format-Collection|Format-Object|Format-Null|Format-Boolean|Format-ScriptBlock|Format-Number|Format-Hashtable|Format-Dictionary|Format-Nicely|Get-DisplayProperty|Get-ShortType|Format-Type), '$12' to replace in VS Code. + "$PSScriptRoot/src/Format2.ps1" "$PSScriptRoot/src/Pester.RSpec.ps1" "$PSScriptRoot/src/Main.ps1" + "$PSScriptRoot/src/functions/assert/*/*" "$PSScriptRoot/src/functions/assertions/*" "$PSScriptRoot/src/functions/*" diff --git a/docs/assertion-types.md b/docs/assertion-types.md new file mode 100644 index 000000000..6e1387048 --- /dev/null +++ b/docs/assertion-types.md @@ -0,0 +1,106 @@ +# Assert assertions + +Pester 6 preview comes with a new set of Should-* assertions. These new assertions are split these categories based on their usage: + +- value + - generic + - type specific + +- collection + - generic + - combinator + +Each of these categories treats `$Actual` and `$Expected` values differently, to provide a consistent behavior when using the `|` syntax. + +## Value vs. Collection assertions + +The `$Actual` value can be provided by two syntaxes, either by pipeline (`|`) or by parameter (`-Actual`): + +```powershell +1 | Should-Be -Expected 1 +Should-Be -Actual 1 -Expected 1 +``` + +### Using pipeline syntax + +When using the pipeline syntax, PowerShell unwraps the input and we lose the type of the collection on the left side. We are provided with a collection that can be either $null, empty or have items. Notably, we cannot distinguish between a single value being provided, and an array of single item: + +```powershell +1 | Should-Be +@(1) | Should-Be +``` + +These will both be received by the assertion as `@(1)`. + +For this reason a value assertion will handle this as `1`, but a collection assertion will handle this input as `@(1)`. + +Another special case is `@()`. A value assertion will handle it as `$null`, but a collection assertion will handle it as `@()`. + +`$null` remains `$null` in both cases. + +```powershell +# Should-Be is a value assertion: +1 | Should-Be -Expected 1 +@(1) | Should-Be -Expected 1 +$null | Should-Be -Expected $null +@() | Should-Be -Expected $null #< --- TODO: this is not the case right now, we special case this as empty array, but is that correct? it does not play well with the value and collection assertion, and we special case it just because we can. +# $null | will give $local:input -> $null , and @() | will give $local:input -> @(), is that distinction important when we know that we will only check against values? + +# This fails, because -Expected does not allow collections. +@() | Should-Be -Expected @() + + + +```powershell +# Should-BeCollection is a collection assertion: +1 | Should-BeCollection -Expected @(1) +@(1) | Should-BeCollection -Expected @(1) +@() | Should-BeCollection -Expected @() + +# This fails, because -Expected requires a collection. +$null | Should-BeCollection -Expected $null +``` + +### Using the -Actual syntax + +The value provides to `-Actual`, is always exactly the same as provided. + +```powershell +Should-Be -Actual 1 -Expected 1 + +# This fails, Actual is collection, while expected is int. +Should-Be -Actual @(1) -Expected 1 +``` + +## Value assertions + +### Generic value assertions + +Generic value assertions, such as `Should-Be`, are for asserting on a single value. They behave quite similar to PowerShell operators, e.g. `Should-Be` maps to `-eq`. + +The `$Expected` accepts any input that is not a collection. +The type of `$Expected` determines the type to be used for the comparison. +`$Actual` is automatically converted to that type. + +```powershell +1 | Should-Be -Expected $true +Get-Process -Name Idle | Should-Be -Expected "System.Diagnostics.Process (Idle)" +``` + +The assertions in the above examples will both pass: +- `1` converts to `bool` `$true`, which is the expected value. +- `Get-Process` retrieves the Idle process (on Windows). This process object gets converted to `string`. The string is equal to the expected value. + +### Type specific value assertions + +Type specific assertions are for asserting on a single value of a given type. For example boolean. These assertions take the advantage of being more specialized, to provide a type specific functionality. Such as `Should-BeString -IgnoreWhitespace`. + +The `$Expected` accepts input that has the same type as the assertion type. E.g. `Should-BeString -Expected "my string"`. + +`$Actual` accepts input that has the same type as the assertion type. The input is not automatically converted to the destination type, unless the assertion specifies it, e.g. `Should-BeFalsy` will convert to `bool`. + +## Collection assertions + + + +These assertions are exported from the module as Assert-* functions and aliased to Should-*, this is because of PowerShell restricting multi word functions to a list of predefined approved verbs. diff --git a/global.json b/global.json index 869f89528..2d566bd8c 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,7 @@ { "sdk": { "rollForward": "latestFeature", - "version": "8.0.100" + "version": "8.0.100", + "allowPrerelease": false } } \ No newline at end of file diff --git a/src/Format2.ps1 b/src/Format2.ps1 new file mode 100644 index 000000000..a4a29d20c --- /dev/null +++ b/src/Format2.ps1 @@ -0,0 +1,194 @@ +function Format-Collection2 ($Value, [switch]$Pretty) { + $length = 0 + $o = foreach ($v in $Value) { + $formatted = Format-Nicely2 -Value $v -Pretty:$Pretty + $length += $formatted.Length + 1 # 1 is for the separator + $formatted + } + + $prettyLimit = 50 + if ($Pretty -and ($length + 3) -gt $prettyLimit) { + # 3 is for the '@()' + "@(`n $($o -join ",`n ")`n)" + } + else { + "@($($o -join ', '))" + } +} + +function Format-Object2 ($Value, $Property, [switch]$Pretty) { + if ($null -eq $Property) { + $Property = foreach ($p in $Value.PSObject.Properties) { $p.Name } + } + $orderedProperty = foreach ($p in $Property | & $SafeCommands['Sort-Object']) { + # force the values to be strings for powershell v2 + "$p" + } + + $valueType = Get-ShortType $Value + $items = foreach ($p in $orderedProperty) { + $v = ([PSObject]$Value.$p) + $f = Format-Nicely2 -Value $v -Pretty:$Pretty + "$p=$f" + } + + if (0 -eq $Property.Length ) { + $o = "$valueType{}" + } + elseif ($Pretty) { + $o = "$valueType{`n $($items -join ";`n ");`n}" + } + else { + $o = "$valueType{$($items -join '; ')}" + } + + $o +} + +function Format-String2 ($Value) { + if ('' -eq $Value) { + return '' + } + + "'$Value'" +} + +function Format-Null2 { + '$null' +} + +function Format-Boolean2 ($Value) { + '$' + $Value.ToString().ToLower() +} + +function Format-ScriptBlock2 ($Value) { + '{' + $Value + '}' +} + +function Format-Number2 ($Value) { + [string]$Value +} + +function Format-Hashtable2 ($Value) { + $head = '@{' + $tail = '}' + + $entries = foreach ($v in $Value.Keys | & $SafeCommands['Sort-Object']) { + $formattedValue = Format-Nicely2 $Value.$v + "$v=$formattedValue" + } + + $head + ( $entries -join '; ') + $tail +} + +function Format-Dictionary2 ($Value) { + $head = 'Dictionary{' + $tail = '}' + + $entries = foreach ($v in $Value.Keys | & $SafeCommands['Sort-Object'] ) { + $formattedValue = Format-Nicely2 $Value.$v + "$v=$formattedValue" + } + + $head + ( $entries -join '; ') + $tail +} + +function Format-Nicely2 ($Value, [switch]$Pretty) { + if ($null -eq $Value) { + return Format-Null2 -Value $Value + } + + if ($Value -is [bool]) { + return Format-Boolean2 -Value $Value + } + + if ($Value -is [string]) { + return Format-String2 -Value $Value + } + + if ($value -is [type]) { + return Format-Type2 -Value $Value + } + + if (Is-DecimalNumber -Value $Value) { + return Format-Number2 -Value $Value + } + + if (Is-ScriptBlock -Value $Value) { + return Format-ScriptBlock2 -Value $Value + } + + if (Is-Value -Value $Value) { + return $Value + } + + if (Is-Hashtable -Value $Value) { + return Format-Hashtable2 -Value $Value + } + + if (Is-Dictionary -Value $Value) { + return Format-Dictionary2 -Value $Value + } + + if ((Is-DataTable -Value $Value) -or (Is-DataRow -Value $Value)) { + return Format-DataTable2 -Value $Value -Pretty:$Pretty + } + + if (Is-Collection -Value $Value) { + return Format-Collection2 -Value $Value -Pretty:$Pretty + } + + Format-Object2 -Value $Value -Property (Get-DisplayProperty2 $Value.GetType()) -Pretty:$Pretty +} + +function Get-DisplayProperty2 ([Type]$Type) { + # rename to Get-DisplayProperty? + + <# some objects are simply too big to show all of their properties, + so we can create a list of properties to show from an object + maybe the default info from Get-FormatData could be utilized here somehow + so we show only stuff that would normally show in format-table view + leveraging the work PS team already did #> + + # this will become more advanced, basically something along the lines of: + # foreach type, try constructing the type, and if it exists then check if the + # incoming type is assignable to the current type, if so then return the properties, + # this way I can specify the map from the most concrete type to the least concrete type + # and for types that do not exist + + $propertyMap = @{ + 'System.Diagnostics.Process' = 'Id', 'Name' + } + + $propertyMap[$Type.FullName] +} + +function Get-ShortType2 ($Value) { + if ($null -ne $value) { + Format-Type2 $Value.GetType() + } + else { + Format-Type2 $null + } +} + +function Format-Type2 ([Type]$Value) { + if ($null -eq $Value) { + return '[null]' + } + + $type = [string]$Value + + $typeFormatted = $type ` + -replace "^System\." ` + -replace "^Management\.Automation\.PSCustomObject$", "PSObject" ` + -replace "^PSCustomObject$", "PSObject" ` + -replace "^Object\[\]$", "collection" ` + + "[$($typeFormatted)]" +} + +function Format-DataTable2 ($Value) { + return "$Value" +} + diff --git a/src/Module.ps1 b/src/Module.ps1 index cf9eef142..0fbd9b74c 100644 --- a/src/Module.ps1 +++ b/src/Module.ps1 @@ -5,8 +5,48 @@ $script:SafeCommands['Get-MockDynamicParameter'] = $ExecutionContext.SessionStat $script:SafeCommands['Write-PesterDebugMessage'] = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Write-PesterDebugMessage', 'function') $script:SafeCommands['Set-DynamicParameterVariable'] = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Set-DynamicParameterVariable', 'function') -& $SafeCommands['Set-Alias'] 'Add-AssertionOperator' 'Add-ShouldOperator' -& $SafeCommands['Set-Alias'] 'Get-AssertionOperator' 'Get-ShouldOperator' +& $SafeCommands['Set-Alias'] 'Add-AssertionOperator' 'Add-ShouldOperator' +& $SafeCommands['Set-Alias'] 'Get-AssertionOperator' 'Get-ShouldOperator' + +& $SafeCommands['Set-Alias'] 'Should-BeFalse' 'Assert-False' +& $SafeCommands['Set-Alias'] 'Should-BeTrue' 'Assert-True' +& $SafeCommands['Set-Alias'] 'Should-BeFalsy' 'Assert-Falsy' +& $SafeCommands['Set-Alias'] 'Should-BeTruthy' 'Assert-Truthy' +& $SafeCommands['Set-Alias'] 'Should-All' 'Assert-All' +& $SafeCommands['Set-Alias'] 'Should-Any' 'Assert-Any' +& $SafeCommands['Set-Alias'] 'Should-BeCollection' 'Assert-Collection' +& $SafeCommands['Set-Alias'] 'Should-ContainCollection' 'Assert-Contain' +& $SafeCommands['Set-Alias'] 'Should-NotContainCollection' 'Assert-NotContain' +& $SafeCommands['Set-Alias'] 'Should-BeEquivalent' 'Assert-Equivalent' +& $SafeCommands['Set-Alias'] 'Should-Throw' 'Assert-Throw' +& $SafeCommands['Set-Alias'] 'Should-Be' 'Assert-Equal' +& $SafeCommands['Set-Alias'] 'Should-BeGreaterThan' 'Assert-GreaterThan' +& $SafeCommands['Set-Alias'] 'Should-BeGreaterThanOrEqual' 'Assert-GreaterThanOrEqual' +& $SafeCommands['Set-Alias'] 'Should-BeLessThan' 'Assert-LessThan' +& $SafeCommands['Set-Alias'] 'Should-BeLessThanOrEqual' 'Assert-LessThanOrEqual' +& $SafeCommands['Set-Alias'] 'Should-NotBe' 'Assert-NotEqual' +& $SafeCommands['Set-Alias'] 'Should-NotBeNull' 'Assert-NotNull' +& $SafeCommands['Set-Alias'] 'Should-NotBeSame' 'Assert-NotSame' +& $SafeCommands['Set-Alias'] 'Should-NotHaveType' 'Assert-NotType' +& $SafeCommands['Set-Alias'] 'Should-BeNull' 'Assert-Null' +& $SafeCommands['Set-Alias'] 'Should-BeSame' 'Assert-Same' +& $SafeCommands['Set-Alias'] 'Should-HaveType' 'Assert-Type' + +& $SafeCommands['Set-Alias'] 'Should-BeString' 'Assert-StringEqual' +& $SafeCommands['Set-Alias'] 'Should-NotBeString' 'Assert-StringNotEqual' +& $SafeCommands['Set-Alias'] 'Should-BeLikeString' 'Assert-Like' +& $SafeCommands['Set-Alias'] 'Should-NotBeLikeString' 'Assert-NotLike' + +& $SafeCommands['Set-Alias'] 'Should-BeEmptyString' 'Assert-StringEmpty' +& $SafeCommands['Set-Alias'] 'Should-NotBeNullOrWhiteSpaceString' 'Assert-StringNotWhiteSpace' +& $SafeCommands['Set-Alias'] 'Should-NotBeNullOrEmptyString' 'Assert-StringNotEmpty' + +& $SafeCommands['Set-Alias'] 'Should-BeFasterThan' 'Assert-Faster' +& $SafeCommands['Set-Alias'] 'Should-BeSlowerThan' 'Assert-Slower' +& $SafeCommands['Set-Alias'] 'Should-BeBefore' 'Assert-Before' +& $SafeCommands['Set-Alias'] 'Should-BeAfter' 'Assert-After' + + & $SafeCommands['Update-TypeData'] -TypeName PesterConfiguration -TypeConverter 'PesterConfigurationDeserializer' -SerializationDepth 5 -Force & $SafeCommands['Update-TypeData'] -TypeName 'Deserialized.PesterConfiguration' -TargetTypeForDeserialization PesterConfiguration -Force @@ -39,6 +79,47 @@ $script:SafeCommands['Set-DynamicParameterVariable'] = $ExecutionContext.Session 'New-PesterContainer' 'New-PesterConfiguration' + # assert + 'Assert-False' + 'Assert-True' + 'Assert-Falsy' + 'Assert-Truthy' + 'Assert-All' + 'Assert-Any' + 'Assert-Contain' + 'Assert-NotContain' + 'Assert-Collection' + 'Assert-Equivalent' + 'Assert-Throw' + 'Assert-Equal' + 'Assert-GreaterThan' + 'Assert-GreaterThanOrEqual' + 'Assert-LessThan' + 'Assert-LessThanOrEqual' + 'Assert-NotEqual' + 'Assert-NotNull' + 'Assert-NotSame' + 'Assert-NotType' + 'Assert-Null' + 'Assert-Same' + 'Assert-Type' + + 'Assert-Like' + 'Assert-NotLike' + 'Assert-StringEqual' + 'Assert-StringNotEqual' + + 'Assert-StringEmpty' + 'Assert-StringNotWhiteSpace' + 'Assert-StringNotEmpty' + + 'Assert-Faster' + 'Assert-Slower' + 'Assert-Before' + 'Assert-After' + + 'Get-EquivalencyOption' + # export 'Export-NUnitReport' 'ConvertTo-NUnitReport' @@ -56,4 +137,49 @@ $script:SafeCommands['Set-DynamicParameterVariable'] = $ExecutionContext.Session ) -Alias @( 'Add-AssertionOperator' 'Get-AssertionOperator' + + # assertion functions + # bool + 'Should-BeFalse' + 'Should-BeTrue' + 'Should-BeFalsy' + 'Should-BeTruthy' + + # collection + 'Should-All' + 'Should-Any' + 'Should-BeCollection' + 'Should-ContainCollection' + 'Should-NotContainCollection' + 'Should-BeEquivalent' + 'Should-Throw' + 'Should-Be' + 'Should-BeGreaterThan' + 'Should-BeGreaterThanOrEqual' + 'Should-BeLessThan' + 'Should-BeLessThanOrEqual' + 'Should-NotBe' + 'Should-NotBeNull' + 'Should-NotBeSame' + 'Should-NotHaveType' + 'Should-BeNull' + 'Should-BeSame' + 'Should-HaveType' + + # string + 'Should-BeString' + 'Should-NotBeString' + + 'Should-BeEmptyString' + 'Should-NotBeNullOrWhiteSpaceString' + 'Should-NotBeNullOrEmptyString' + + 'Should-BeLikeString' + 'Should-NotBeLikeString' + + # time + 'Should-BeFasterThan' + 'Should-BeSlowerThan' + 'Should-BeBefore' + 'Should-BeAfter' ) diff --git a/src/Pester.psd1 b/src/Pester.psd1 index 8dd6a9f9b..793efbd80 100644 --- a/src/Pester.psd1 +++ b/src/Pester.psd1 @@ -66,6 +66,44 @@ 'New-PesterContainer' 'New-PesterConfiguration' + # assert + 'Assert-False' + 'Assert-True' + 'Assert-Falsy' + 'Assert-Truthy' + 'Assert-All' + 'Assert-Any' + 'Assert-Contain' + 'Assert-NotContain' + 'Assert-Collection' + 'Assert-Equivalent' + 'Assert-Throw' + 'Assert-Equal' + 'Assert-GreaterThan' + 'Assert-GreaterThanOrEqual' + 'Assert-LessThan' + 'Assert-LessThanOrEqual' + 'Assert-NotEqual' + 'Assert-NotNull' + 'Assert-NotSame' + 'Assert-NotType' + 'Assert-Null' + 'Assert-Same' + 'Assert-Type' + 'Assert-Like' + 'Assert-NotLike' + 'Assert-StringEqual' + 'Assert-StringNotEqual' + 'Assert-StringEmpty' + 'Assert-StringNotWhiteSpace' + 'Assert-StringNotEmpty' + 'Assert-Faster' + 'Assert-Slower' + 'Assert-Before' + 'Assert-After' + + 'Get-EquivalencyOption' + # legacy 'Assert-VerifiableMock' 'Assert-MockCalled' @@ -85,6 +123,51 @@ AliasesToExport = @( 'Add-AssertionOperator' 'Get-AssertionOperator' + + # assertion functions + # bool + 'Should-BeFalse' + 'Should-BeTrue' + 'Should-BeFalsy' + 'Should-BeTruthy' + + # collection + 'Should-All' + 'Should-Any' + 'Should-ContainCollection' + 'Should-NotContainCollection' + 'Should-BeCollection' + 'Should-BeEquivalent' + 'Should-Throw' + 'Should-Be' + 'Should-BeGreaterThan' + 'Should-BeGreaterThanOrEqual' + 'Should-BeLessThan' + 'Should-BeLessThanOrEqual' + 'Should-NotBe' + 'Should-NotBeNull' + 'Should-NotBeSame' + 'Should-NotHaveType' + 'Should-BeNull' + 'Should-BeSame' + 'Should-HaveType' + + # string + 'Should-BeString' + 'Should-NotBeString' + + 'Should-BeEmptyString' + + 'Should-NotBeNullOrWhiteSpaceString' + 'Should-NotBeNullOrEmptyString' + + 'Should-BeLikeString' + 'Should-NotBeLikeString' + + 'Should-BeFasterThan' + 'Should-BeSlowerThan' + 'Should-BeBefore' + 'Should-BeAfter' ) diff --git a/src/TypeClass.ps1 b/src/TypeClass.ps1 index 9319cdd64..c097d5f3a 100644 --- a/src/TypeClass.ps1 +++ b/src/TypeClass.ps1 @@ -39,3 +39,11 @@ function Is-Object ($Value) { -not ($null -eq $Value -or (Is-Value -Value $Value) -or (Is-Collection -Value $Value)) } + +function Is-DataRow ($Value) { + $Value -is [Data.DataRow] -or $Value.Psobject.TypeNames[0] -like '*System.Data.DataRow' +} + +function Is-DataTable ($Value) { + $Value -is [Data.DataTable] -or $Value.Psobject.TypeNames[0] -like '*System.Data.DataTable' +} diff --git a/src/functions/assert/Boolean/Should-BeFalse.ps1 b/src/functions/assert/Boolean/Should-BeFalse.ps1 new file mode 100644 index 000000000..94a8e4900 --- /dev/null +++ b/src/functions/assert/Boolean/Should-BeFalse.ps1 @@ -0,0 +1,55 @@ +function Assert-False { + <# + .SYNOPSIS + Compares the actual value to a boolean $false. It does not convert input values to boolean, and will fail for any value that is not $false. + + .PARAMETER Actual + The actual value to compare to $false. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + $false | Should-BeFalse + ``` + + This assertion will pass. + + .EXAMPLE + ```powershell + $true | Should-BeFalse + Get-Process | Should-BeFalse + $null | Should-BeFalse + $() | Should-BeFalse + @() | Should-BeFalse + 0 | Should-BeFalse + ``` + + All of these assertions will fail, because the actual value is not $false. + + .NOTES + The `Should-BeFalse` assertion is the opposite of the `Should-BeTrue` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeFalse + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ($Actual -isnot [bool] -or $Actual) { + $Message = Get-AssertionMessage -Expected $false -Actual $Actual -Because $Because -DefaultMessage "Expected , but got: ." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Boolean/Should-BeFalsy.ps1 b/src/functions/assert/Boolean/Should-BeFalsy.ps1 new file mode 100644 index 000000000..d937516f0 --- /dev/null +++ b/src/functions/assert/Boolean/Should-BeFalsy.ps1 @@ -0,0 +1,55 @@ +function Assert-Falsy { + <# + .SYNOPSIS + Compares the actual value to a boolean $false or a falsy value: 0, "", $null or @(). It converts the input value to a boolean. + + .PARAMETER Actual + The actual value to compare to $false. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + $false | Should-BeFalsy + $null | Should-BeFalsy + $() | Should-BeFalsy + @() | Should-BeFalsy + 0 | Should-BeFalsy + ``` + + These assertion will pass. + + .EXAMPLE + ```powershell + $true | Should-BeFalsy + Get-Process | Should-BeFalsy + ``` + + These assertions will fail, because the actual value is not $false or falsy. + + .NOTES + The `Should-BeFalsy` assertion is the opposite of the `Should-BeTruthy` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeFalsy + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ($Actual) { + $Message = Get-AssertionMessage -Expected $false -Actual $Actual -Because $Because -DefaultMessage 'Expected or a falsy value: 0, "", $null or @(), but got: .' + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Boolean/Should-BeTrue.ps1 b/src/functions/assert/Boolean/Should-BeTrue.ps1 new file mode 100644 index 000000000..fab550f97 --- /dev/null +++ b/src/functions/assert/Boolean/Should-BeTrue.ps1 @@ -0,0 +1,55 @@ +function Assert-True { + <# + .SYNOPSIS + Compares the actual value to a boolean $true. It does not convert input values to boolean, and will fail for any value is not $true. + + .PARAMETER Actual + The actual value to compare to $true. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + $true | Should-BeTrue + ``` + + This assertion will pass. + + .EXAMPLE + ```powershell + $false | Should-BeTrue + Get-Process | Should-BeTrue + $null | Should-BeTrue + $() | Should-BeTrue + @() | Should-BeTrue + 0 | Should-BeTrue + ``` + + All of these assertions will fail, because the actual value is not $true. + + .NOTES + The `Should-BeTrue` assertion is the opposite of the `Should-BeFalse` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeTrue + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ($Actual -isnot [bool] -or -not $Actual) { + $Message = Get-AssertionMessage -Expected $true -Actual $Actual -Because $Because -DefaultMessage "Expected , but got: ." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Boolean/Should-BeTruthy.ps1 b/src/functions/assert/Boolean/Should-BeTruthy.ps1 new file mode 100644 index 000000000..28538ab7c --- /dev/null +++ b/src/functions/assert/Boolean/Should-BeTruthy.ps1 @@ -0,0 +1,56 @@ +function Assert-Truthy { + <# + .SYNOPSIS + Compares the actual value to a boolean $true. It converts input values to boolean, and will fail for any value is not $true, or truthy. + + .PARAMETER Actual + The actual value to compare to $true. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + $true | Should-BeTruthy + 1 | Should-BeTruthy + Get-Process | Should-BeTruthy + ``` + + This assertion will pass. + + .EXAMPLE + ```powershell + $false | Should-BeTruthy + $null | Should-BeTruthy + $() | Should-BeTruthy + @() | Should-BeTruthy + 0 | Should-BeTruthy + ``` + + All of these assertions will fail, because the actual value is not $true or truthy. + + .NOTES + The `Should-BeTruthy` assertion is the opposite of the `Should-BeFalsy` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeTruthy + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if (-not $Actual) { + $Message = Get-AssertionMessage -Expected $true -Actual $Actual -Because $Because -DefaultMessage "Expected or a truthy value, but got: ." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Collection/Should-All.ps1 b/src/functions/assert/Collection/Should-All.ps1 new file mode 100644 index 000000000..126a7db6a --- /dev/null +++ b/src/functions/assert/Collection/Should-All.ps1 @@ -0,0 +1,104 @@ +function Assert-All { + <# + .SYNOPSIS + Compares all items in a collection to a filter script. If the filter returns true, or does not throw for all the items in the collection, the assertion passes. + + .PARAMETER FilterScript + A script block that filters the input collection. The script block can use Should-* assertions or throw exceptions to indicate failure. + + .PARAMETER Actual + A collection of items to filter. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-All { $_ -gt 0 } + 1, 2, 3 | Should-All { $_ | Should-BeGreaterThan 0 } + ``` + + This assertion will pass, because all items pass the filter. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-All { $_ -gt 1 } + 1, 2, 3 | Should-All { $_ | Should-BeGreaterThan 1 } + ``` + + The assertions will fail because not all items in the array are greater than 1. + + .LINK + https://pester.dev/docs/commands/Should-All + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + [CmdletBinding()] + param ( + [Parameter(ValueFromPipeline = $true, Position = 1)] + $Actual, + [Parameter(Position = 0, Mandatory)] + [scriptblock]$FilterScript, + [String]$Because + ) + + + $Expected = $FilterScript + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput + $Actual = $collectedInput.Actual + + if ($null -eq $Actual -or 0 -eq @($Actual).Count) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Data $data -Because $Because -DefaultMessage "Expected all items in collection to pass filter , but contains no items to compare." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $failReasons = $null + $appendMore = $false + # we are jumping between modules so I need to explicitly pass the _ variable + # simply using '&' won't work + # see: https://blogs.msdn.microsoft.com/sergey_babkins_blog/2014/10/30/calling-the-script-blocks-in-powershell/ + $actualFiltered = foreach ($item in $Actual) { + $underscore = [PSVariable]::new('_', $item) + try { + $pass = $FilterScript.InvokeWithContext($null, $underscore, $null) + } + catch { + if ($null -eq $failReasons) { + $failReasons = [System.Collections.Generic.List[string]]::new(10) + } + if ($failReasons.Count -lt 10) { + $failReasons.Add($_.Exception.InnerException.Message) + } + else { + $appendMore = $true + } + + $pass = $false + } + if (-not $pass) { $item } + } + + # Make sure are checking the count of the filtered items, not just truthiness of a single item. + $actualFiltered = @($actualFiltered) + if (0 -lt $actualFiltered.Count) { + $data = @{ + actualFiltered = if (1 -eq $actualFiltered.Count) { $actualFiltered[0] } else { $actualFiltered } + actualFilteredCount = $actualFiltered.Count + } + + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Data $data -Because $Because -DefaultMessage "Expected all items in collection to pass filter , but of them did not pass the filter." + if ($null -ne $failReasons) { + $failReasons = $failReasons -join "`n" + if ($appendMore) { + $failReasons += "`nand more..." + } + $Message += "`nReasons :`n$failReasons" + } + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Collection/Should-Any.ps1 b/src/functions/assert/Collection/Should-Any.ps1 new file mode 100644 index 000000000..f546babfd --- /dev/null +++ b/src/functions/assert/Collection/Should-Any.ps1 @@ -0,0 +1,93 @@ +function Assert-Any { + <# + .SYNOPSIS + Compares all items in a collection to a filter script. If the filter returns true, or does not throw for any of the items in the collection, the assertion passes. + + .PARAMETER FilterScript + A script block that filters the input collection. The script block can use Should-* assertions or throw exceptions to indicate failure. + + .PARAMETER Actual + A collection of items to filter. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-Any { $_ -gt 2 } + 1, 2, 3 | Should-Any { $_ | Should-BeGreaterThan 2 } + ``` + + This assertion will pass, because at least one item in the collection passed the filter. 3 is greater than 2. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-Any { $_ -gt 4 } + 1, 2, 3 | Should-Any { $_ | Should-BeGreaterThan 4 } + ``` + + The assertions will fail because none of theitems in the array are greater than 4. + + .LINK + https://pester.dev/docs/commands/Should-Any + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(ValueFromPipeline = $true, Position = 1)] + $Actual, + [Parameter(Position = 0, Mandatory)] + [scriptblock]$FilterScript, + [String]$Because + ) + + $Expected = $FilterScript + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput + $Actual = $collectedInput.Actual + + if ($null -eq $Actual -or 0 -eq @($Actual).Count) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Data $data -Because $Because -DefaultMessage "Expected at least one item in collection to pass filter , but contains no items to compare." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $failReasons = $null + $appendMore = $false + $pass = $false + foreach ($item in $Actual) { + $underscore = [PSVariable]::new('_', $item) + try { + $pass = $FilterScript.InvokeWithContext($null, $underscore, $null) + } + catch { + if ($null -eq $failReasons) { + $failReasons = [System.Collections.Generic.List[string]]::new(10) + } + if ($failReasons.Count -lt 10) { + $failReasons.Add($_.Exception.InnerException.Message) + } + else { + $appendMore = $true + } + + $pass = $false + } + if ($pass) { break } + } + + if (-not $pass) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected at least one item in collection to pass filter , but none of the items passed the filter." + if ($null -ne $failReasons) { + $failReasons = $failReasons -join "`n" + if ($appendMore) { + $failReasons += "`nand more..." + } + $Message += "`nReasons :`n$failReasons" + } + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Collection/Should-BeCollection.ps1 b/src/functions/assert/Collection/Should-BeCollection.ps1 new file mode 100644 index 000000000..93624ec20 --- /dev/null +++ b/src/functions/assert/Collection/Should-BeCollection.ps1 @@ -0,0 +1,73 @@ +function Assert-Collection { + <# + .SYNOPSIS + Compares collections for equality, by comparing their sizes and each item in them. It does not compare the types of the input collections. + + .PARAMETER Expected + A collection of items. + + .PARAMETER Actual + A collection of items. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-BeCollection @(1, 2, 3) + @(1) | Should-BeCollection @(1) + 1 | Should-BeCollection 1 + ``` + + This assertion will pass, because the collections have the same size and the items are equal. + + .EXAMPLE + ```powershell + 1, 2, 3, 4 | Should-BeCollection @(1, 2, 3) + 1, 2, 3, 4 | Should-BeCollection @(5, 6, 7, 8) + @(1) | Should-BeCollection @(2) + 1 | Should-BeCollection @(2) + ``` + + The assertions will fail because the collections are not equal. + + .LINK + https://pester.dev/docs/commands/Should-BeCollection + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput + $Actual = $collectedInput.Actual + + if (-not (Is-Collection -Value $Expected)) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected '' is not a collection." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + if (-not (Is-Collection -Value $Actual)) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Actual '' is not a collection." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + if (-not (Is-CollectionSize -Expected $Expected -Actual $Actual)) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected '' to be equal to collection '' but they don't have the same number of items." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + if ($Actual) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected '' to be present in collection '', but it was not there." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Collection/Should-ContainCollection.ps1 b/src/functions/assert/Collection/Should-ContainCollection.ps1 new file mode 100644 index 000000000..f74b57ebc --- /dev/null +++ b/src/functions/assert/Collection/Should-ContainCollection.ps1 @@ -0,0 +1,56 @@ +function Assert-Contain { + <# + .SYNOPSIS + Compares collections to see if the expected collection is present in the provided collection. It does not compare the types of the input collections. + + .PARAMETER Expected + A collection of items. + + .PARAMETER Actual + A collection of items. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-ContainCollection @(1, 2) + @(1) | Should-ContainCollection @(1) + ``` + + This assertion will pass, because all items are present in the collection, in the right order. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-ContainCollection @(3, 4) + 1, 2, 3 | Should-ContainCollection @(3, 2, 1) + @(1) | Should-ContainCollection @(2) + ``` + + This assertion will fail, because not all items are present in the collection, or are not in the right order. + + .LINK + https://pester.dev/docs/commands/Should-ContainCollection + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput + $Actual = $collectedInput.Actual + if ($Actual -notcontains $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected to be present in collection , but it was not there." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Collection/Should-NotContainCollection.ps1 b/src/functions/assert/Collection/Should-NotContainCollection.ps1 new file mode 100644 index 000000000..57a9e2555 --- /dev/null +++ b/src/functions/assert/Collection/Should-NotContainCollection.ps1 @@ -0,0 +1,56 @@ +function Assert-NotContain { + <# + .SYNOPSIS + Compares collections to ensure that the expected collection is not present in the provided collection. It does not compare the types of the input collections. + + .PARAMETER Expected + A collection of items. + + .PARAMETER Actual + A collection of items. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-ContainCollection @(3, 4) + 1, 2, 3 | Should-ContainCollection @(3, 2, 1) + @(1) | Should-ContainCollection @(2) + ``` + + This assertion will pass, because the collections are different, or the items are not in the right order. + + .EXAMPLE + ```powershell + 1, 2, 3 | Should-NotContainCollection @(1, 2) + @(1) | Should-NotContainCollection @(1) + ``` + + This assertion will fail, because all items are present in the collection and are in the right order. + + .LINK + https://pester.dev/docs/commands/Should-NotContainCollection + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput + $Actual = $collectedInput.Actual + if ($Actual -contains $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected to not be present in collection , but it was there." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/Common/Collect-Input.ps1 b/src/functions/assert/Common/Collect-Input.ps1 new file mode 100644 index 000000000..4180a4eb0 --- /dev/null +++ b/src/functions/assert/Common/Collect-Input.ps1 @@ -0,0 +1,50 @@ +function Collect-Input { + param ( + # This is input when called without pipeline syntax e.g. Should-Be -Actual 1 + # In that case -ParameterInput $Actual will be 1 + $ParameterInput, + # This is $local:input, which is the input that powershell collected from pipeline. + # It is always $null or object[] containing all the received items. + $PipelineInput, + # This tell us if we were called by | syntax or not. Caller needs to pass in $MyInvocation.ExpectingInput. + [Parameter(Mandatory)] + [bool] $IsPipelineInput, + # This unwraps input provided by |. The effect of this is that we get single item input directly, + # and not wrapped in array. E.g. 1 | Should-Be -> 1, and not 1 | Should-Be -> @(1). + # + # Single item assertions should always provide this parameter. Collection assertions should never + # provide this parameter, because they should handle collections consistenly. + # + # This parameter does not apply to input provided by parameter sytax Should-Be -Actual 1 + [switch] $UnrollInput + ) + + if ($IsPipelineInput) { + # We are called like this: 1 | Assert-Equal -Expected 1, we will get $local:Input in $PipelineInput and $true in $IsPipelineInput (coming from $MyInvocation.ExpectingInput). + + if ($PipelineInput.Count -eq 0) { + # When calling @() | Assert-Equal -Expected 1, the engine will special case it, and we will get empty array in $local:Input + $collectedInput = @() + } + else { + if ($UnrollInput) { + # This is array of all the input, unwrap it. + $collectedInput = foreach ($item in $PipelineInput) { $item } + } + else { + # This is array of all the input. + $collectedInput = $PipelineInput + } + } + } + else { + # This is exactly what was provided to the ActualParmeter. + $collectedInput = $ParameterInput + } + + @{ + Actual = $collectedInput + # We can use this to determine if collections are comparable. Pipeline input will unwind the collection, so pipeline input collection type is not comparable. + IsPipelineInput = $IsPipelineInput + } +} diff --git a/src/functions/assert/Common/Ensure-ExpectedIsNotCollection.ps1 b/src/functions/assert/Common/Ensure-ExpectedIsNotCollection.ps1 new file mode 100644 index 000000000..83d174ab0 --- /dev/null +++ b/src/functions/assert/Common/Ensure-ExpectedIsNotCollection.ps1 @@ -0,0 +1,12 @@ +function Ensure-ExpectedIsNotCollection { + param( + $InputObject + ) + + if (Is-Collection $InputObject) + { + throw [ArgumentException]'You provided a collection to the -Expected parameter. Using a collection on the -Expected side is not allowed by this assertion, because it leads to unexpected behavior. Please use Should-Any, Should-All or some other specialized collection assertion.' + } + + $InputObject +} diff --git a/src/functions/assert/Common/Get-AssertionMessage.ps1 b/src/functions/assert/Common/Get-AssertionMessage.ps1 new file mode 100644 index 000000000..ba25a5b52 --- /dev/null +++ b/src/functions/assert/Common/Get-AssertionMessage.ps1 @@ -0,0 +1,40 @@ +function Get-AssertionMessage ($Expected, $Actual, $Because, $Option, [hashtable]$Data = @{}, $CustomMessage, $DefaultMessage, [switch]$Pretty) { + if (-not $CustomMessage) { + $CustomMessage = $DefaultMessage + } + + $expectedFormatted = Format-Nicely2 -Value $Expected -Pretty:$Pretty + $actualFormatted = Format-Nicely2 -Value $Actual -Pretty:$Pretty + $becauseFormatted = Format-Because -Because $Because + + $optionMessage = $null; + if ($null -ne $Option -and $option.Length -gt 0) { + if (-not $Pretty) { + $optionMessage = "Used options: $($Option -join ", ")." + } + else { + if ($Pretty) { + $optionMessage = "Used options:$(foreach ($o in $Option) { "`n$o" })." + } + } + } + + + $CustomMessage = $CustomMessage.Replace('', $expectedFormatted) + $CustomMessage = $CustomMessage.Replace('', $actualFormatted) + $CustomMessage = $CustomMessage.Replace('', (Get-ShortType2 -Value $Expected)) + $CustomMessage = $CustomMessage.Replace('', (Get-ShortType2 -Value $Actual)) + $CustomMessage = $CustomMessage.Replace('', $optionMessage) + $CustomMessage = $CustomMessage.Replace('', $becauseFormatted) + + foreach ($pair in $Data.GetEnumerator()) { + $CustomMessage = $CustomMessage.Replace("<$($pair.Key)>", (Format-Nicely2 -Value $pair.Value)) + } + + if (-not $Pretty) { + $CustomMessage + } + else { + $CustomMessage + "`n`n" + } +} diff --git a/src/functions/assert/Common/Get-CustomFailureMessage.ps1 b/src/functions/assert/Common/Get-CustomFailureMessage.ps1 new file mode 100644 index 000000000..f2b5f179a --- /dev/null +++ b/src/functions/assert/Common/Get-CustomFailureMessage.ps1 @@ -0,0 +1,6 @@ +function Get-CustomFailureMessage ($CustomMessage, $Expected, $Actual) +{ + $formatted = $CustomMessage -f $Expected, $Actual + $tokensReplaced = $formatted -replace '', $Expected -replace '', $Actual + $tokensReplaced -replace '', $Expected -replace '', $Actual +} \ No newline at end of file diff --git a/src/functions/assert/Equivalence/Should-BeEquivalent.ps1 b/src/functions/assert/Equivalence/Should-BeEquivalent.ps1 new file mode 100644 index 000000000..f8bd218c9 --- /dev/null +++ b/src/functions/assert/Equivalence/Should-BeEquivalent.ps1 @@ -0,0 +1,807 @@ +function Test-Same ($Expected, $Actual) { + [object]::ReferenceEquals($Expected, $Actual) +} + +function Is-CollectionSize ($Expected, $Actual) { + if ($Expected.Length -is [Int] -and $Actual.Length -is [Int]) { + return $Expected.Length -eq $Actual.Length + } + else { + return $Expected.Count -eq $Actual.Count + } +} + +function Is-DataTableSize ($Expected, $Actual) { + return $Expected.Rows.Count -eq $Actual.Rows.Count +} + +function Get-ValueNotEquivalentMessage ($Expected, $Actual, $Property, $Options) { + $Expected = Format-Nicely2 -Value $Expected + $Actual = Format-Nicely2 -Value $Actual + $propertyInfo = if ($Property) { " property $Property with value" } + $comparison = if ("Equality" -eq $Options.Comparator) { 'equal' } else { 'equivalent' } + "Expected$propertyInfo $Expected to be $comparison to the actual value, but got $Actual." +} + + +function Get-CollectionSizeNotTheSameMessage ($Actual, $Expected, $Property) { + $expectedLength = if ($Expected.Length -is [int]) { $Expected.Length } else { $Expected.Count } + $actualLength = if ($Actual.Length -is [int]) { $Actual.Length } else { $Actual.Count } + $Expected = Format-Collection2 -Value $Expected + $Actual = Format-Collection2 -Value $Actual + + $propertyMessage = $null + if ($property) { + $propertyMessage = " in property $Property with values" + } + "Expected collection$propertyMessage $Expected with length $expectedLength to be the same size as the actual collection, but got $Actual with length $actualLength." +} + +function Get-DataTableSizeNotTheSameMessage ($Actual, $Expected, $Property) { + $expectedLength = $Expected.Rows.Count + $actualLength = $Actual.Rows.Count + $Expected = Format-Collection2 -Value $Expected + $Actual = Format-Collection2 -Value $Actual + + $propertyMessage = $null + if ($property) { + $propertyMessage = " in property $Property with values" + } + "Expected DataTable$propertyMessage $Expected with length $expectedLength to be the same size as the actual DataTable, but got $Actual with length $actualLength." +} + +function Compare-CollectionEquivalent ($Expected, $Actual, $Property, $Options) { + if (-not (Is-Collection -Value $Expected)) { + throw [ArgumentException]"Expected must be a collection." + } + + if (-not (Is-Collection -Value $Actual)) { + Write-EquivalenceResult -Difference "`$Actual is not a collection it is a $(Format-Nicely2 $Actual.GetType()), so they are not equivalent." + $expectedFormatted = Format-Collection2 -Value $Expected + $expectedLength = $expected.Length + $actualFormatted = Format-Nicely2 -Value $actual + return "Expected collection $expectedFormatted with length $expectedLength, but got $actualFormatted." + } + + if (-not (Is-CollectionSize -Expected $Expected -Actual $Actual)) { + Write-EquivalenceResult -Difference "`$Actual does not have the same size ($($Actual.Length)) as `$Expected ($($Expected.Length)) so they are not equivalent." + return Get-CollectionSizeNotTheSameMessage -Expected $Expected -Actual $Actual -Property $Property + } + + $eEnd = if ($Expected.Length -is [int]) { $Expected.Length } else { $Expected.Count } + $aEnd = if ($Actual.Length -is [int]) { $Actual.Length } else { $Actual.Count } + Write-EquivalenceResult "Comparing items in collection, `$Expected has lenght $eEnd, `$Actual has length $aEnd." + $taken = @() + $notFound = @() + $anyDifferent = $false + for ($e = 0; $e -lt $eEnd; $e++) { + # todo: retest strict order + Write-EquivalenceResult "`nSearching for `$Expected[$e]:" + $currentExpected = $Expected[$e] + $found = $false + if ($StrictOrder) { + $currentActual = $Actual[$e] + if ($taken -notcontains $e -and (-not (Compare-Equivalent -Expected $currentExpected -Actual $currentActual -Path $Property -Options $Options))) { + $taken += $e + $found = $true + Write-EquivalenceResult -Equivalence "`Found `$Expected[$e]." + } + } + else { + for ($a = 0; $a -lt $aEnd; $a++) { + # we already took this item as equivalent to an item + # in the expected collection, skip it + if ($taken -contains $a) { + Write-EquivalenceResult "Skipping `$Actual[$a] because it is already taken." + continue + } + $currentActual = $Actual[$a] + # -not, because $null means no differences, and some strings means there are differences + Write-EquivalenceResult "Comparing `$Actual[$a] to `$Expected[$e] to see if they are equivalent." + if (-not (Compare-Equivalent -Expected $currentExpected -Actual $currentActual -Path $Property -Options $Options)) { + # add the index to the list of taken items so we can skip it + # in the search, this way we can compare collections with + # arrays multiple same items + $taken += $a + $found = $true + Write-EquivalenceResult -Equivalence "`Found equivalent item for `$Expected[$e] at `$Actual[$a]." + # we already found the item we + # can move on to the next item in Exected array + break + } + } + } + if (-not $found) { + Write-EquivalenceResult -Difference "`$Actual does not contain `$Expected[$e]." + $anyDifferent = $true + $notFound += $currentExpected + } + } + + # do not depend on $notFound collection here + # failing to find a single $null, will return + # @($null) which evaluates to false, even though + # there was a single item that we did not find + if ($anyDifferent) { + Write-EquivalenceResult -Difference "`$Actual and `$Expected arrays are not equivalent." + $Expected = Format-Nicely2 -Value $Expected + $Actual = Format-Nicely2 -Value $Actual + $notFoundFormatted = Format-Nicely2 -Value $notFound + + $propertyMessage = if ($Property) { " in property $Property which is" } + return "Expected collection$propertyMessage $Expected to be equivalent to $Actual but some values were missing: $notFoundFormatted." + } + Write-EquivalenceResult -Equivalence "`$Actual and `$Expected arrays are equivalent." +} + +function Compare-DataTableEquivalent ($Expected, $Actual, $Property, $Options) { + if (-not (Is-DataTable -Value $Expected)) { + throw [ArgumentException]"Expected must be a DataTable." + } + + if (-not (Is-DataTable -Value $Actual)) { + $expectedFormatted = Format-Collection2 -Value $Expected + $expectedLength = $expected.Rows.Count + $actualFormatted = Format-Nicely2 -Value $actual + return "Expected DataTable $expectedFormatted with length $expectedLength, but got $actualFormatted." + } + + if (-not (Is-DataTableSize -Expected $Expected -Actual $Actual)) { + return Get-DataTableSizeNotTheSameMessage -Expected $Expected -Actual $Actual -Property $Property + } + + $eEnd = $Expected.Rows.Count + $aEnd = $Actual.Rows.Count + $taken = @() + $notFound = @() + for ($e = 0; $e -lt $eEnd; $e++) { + $currentExpected = $Expected.Rows[$e] + $found = $false + if ($StrictOrder) { + $currentActual = $Actual.Rows[$e] + if ((-not (Compare-Equivalent -Expected $currentExpected -Actual $currentActual -Path $Property -Options $Options)) -and $taken -notcontains $e) { + $taken += $e + $found = $true + } + } + else { + for ($a = 0; $a -lt $aEnd; $a++) { + $currentActual = $Actual.Rows[$a] + if ((-not (Compare-Equivalent -Expected $currentExpected -Actual $currentActual -Path $Property -Options $Options)) -and $taken -notcontains $a) { + $taken += $a + $found = $true + } + } + } + if (-not $found) { + $notFound += $currentExpected + } + } + $Expected = Format-Nicely2 -Value $Expected + $Actual = Format-Nicely2 -Value $Actual + $notFoundFormatted = Format-Nicely2 -Value ( $notFound | & $SafeCommands['ForEach-Object'] { Format-Nicely2 -Value $_ } ) + + if ($notFound) { + $propertyMessage = if ($Property) { " in property $Property which is" } + return "Expected DataTable$propertyMessage $Expected to be equivalent to $Actual but some values were missing: $notFoundFormatted." + } +} + +function Compare-ValueEquivalent ($Actual, $Expected, $Property, $Options) { + $Expected = $($Expected) + if (-not (Is-Value -Value $Expected)) { + throw [ArgumentException]"Expected must be a Value." + } + + # we don't specify the options in some tests so here we make + # sure that equivalency is used as the default + # not ideal but better than rewriting 100 tests + if (($null -eq $Options) -or + ($null -eq $Options.Comparator) -or + ("Equivalency" -eq $Options.Comparator)) { + Write-EquivalenceResult "Equivalency comparator is used, values will be compared for equivalency." + # fix that string 'false' becomes $true boolean + if ($Actual -is [Bool] -and $Expected -is [string] -and "$Expected" -eq 'False') { + Write-EquivalenceResult "`$Actual is a boolean, and `$Expected is a 'False' string, which we consider equivalent to boolean `$false. Setting `$Expected to `$false." + $Expected = $false + if ($Expected -ne $Actual) { + Write-EquivalenceResult -Difference "`$Actual is not equivalent to $(Format-Nicely2 $Expected) because it is $(Format-Nicely2 $Actual)." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Property -Options $Options + } + Write-EquivalenceResult -Equivalence "`$Actual is equivalent to $(Format-Nicely2 $Expected) because it is $(Format-Nicely2 $Actual)." + return + } + + if ($Expected -is [Bool] -and $Actual -is [string] -and "$Actual" -eq 'False') { + Write-EquivalenceResult "`$Actual is a 'False' string, which we consider equivalent to boolean `$false. `$Expected is a boolean. Setting `$Actual to `$false." + $Actual = $false + if ($Expected -ne $Actual) { + Write-EquivalenceResult -Difference "`$Actual is not equivalent to $(Format-Nicely2 $Expected) because it is $(Format-Nicely2 $Actual)." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Property -Options $Options + } + Write-EquivalenceResult -Equivalence "`$Actual is equivalent to $(Format-Nicely2 $Expected) because it is $(Format-Nicely2 $Actual)." + return + } + + # fix that scriptblocks are compared by reference + if (Is-ScriptBlock -Value $Expected) { + Write-EquivalenceResult "`$Expected is a ScriptBlock, scriptblocks are considered equivalent when their content is equal. Converting `$Expected to string." + # forcing scriptblock to serialize to string and then comparing that + if ("$Expected" -ne $Actual) { + # todo: difference on index? + Write-EquivalenceResult -Difference "`$Actual is not equivalent to `$Expected because their contents differ." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Path -Options $Options + } + Write-EquivalenceResult -Equivalence "`$Actual is equivalent to `$Expected because their contents are equal." + return + } + } + else { + Write-EquivalenceResult "Equality comparator is used, values will be compared for equality." + } + + Write-EquivalenceResult "Comparing values as $(Format-Nicely2 $Expected.GetType()) because `$Expected has that type." + # todo: shorter messages when both sides have the same type (do not compare by using -is, instead query the type and compare it) because -is is true even for parent types + $type = $Expected.GetType() + $coalescedActual = $Actual -as $type + if ($Expected -ne $Actual) { + Write-EquivalenceResult -Difference "`$Actual is not equivalent to $(Format-Nicely2 $Expected) because it is $(Format-Nicely2 $Actual), and $(Format-Nicely2 $Actual) coalesced to $(Format-Nicely2 $type) is $(Format-Nicely2 $coalescedActual)." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Property -Options $Options + } + Write-EquivalenceResult -Equivalence "`$Actual is equivalent to $(Format-Nicely2 $Expected) because it is $(Format-Nicely2 $Actual), and $(Format-Nicely2 $Actual) coalesced to $(Format-Nicely2 $type) is $(Format-Nicely2 $coalescedActual)." +} + +function Compare-HashtableEquivalent ($Actual, $Expected, $Property, $Options) { + if (-not (Is-Hashtable -Value $Expected)) { + throw [ArgumentException]"Expected must be a hashtable." + } + + if (-not (Is-Hashtable -Value $Actual)) { + Write-EquivalenceResult -Difference "`$Actual is not a hashtable it is a $(Format-Nicely2 $Actual.GetType()), so they are not equivalent." + $expectedFormatted = Format-Nicely2 -Value $Expected + $actualFormatted = Format-Nicely2 -Value $Actual + return "Expected hashtable $expectedFormatted, but got $actualFormatted." + } + + # todo: if either side or both sides are empty hashtable make the verbose output shorter and nicer + + $actualKeys = $Actual.Keys + $expectedKeys = $Expected.Keys + + Write-EquivalenceResult "`Comparing all ($($expectedKeys.Count)) keys from `$Expected to keys in `$Actual." + $result = @() + foreach ($k in $expectedKeys) { + if (-not (Test-IncludedPath -PathSelector Hashtable -Path $Property -Options $Options -InputObject $k)) { + continue + } + + $actualHasKey = $actualKeys -contains $k + if (-not $actualHasKey) { + Write-EquivalenceResult -Difference "`$Actual is missing key '$k'." + $result += "Expected has key '$k' that the other object does not have." + continue + } + + $expectedValue = $Expected[$k] + $actualValue = $Actual[$k] + Write-EquivalenceResult "Both `$Actual and `$Expected have key '$k', comparing thier contents." + $result += Compare-Equivalent -Expected $expectedValue -Actual $actualValue -Path "$Property.$k" -Options $Options + } + + if (!$Options.ExcludePathsNotOnExpected) { + # fix for powershell 2 where the array needs to be explicit + $keysNotInExpected = @( $actualKeys | & $SafeCommands['Where-Object'] { $expectedKeys -notcontains $_ }) + + $filteredKeysNotInExpected = @( $keysNotInExpected | Test-IncludedPath -PathSelector Hashtable -Path $Property -Options $Options) + + # fix for powershell v2 where foreach goes once over null + if ($filteredKeysNotInExpected | & $SafeCommands['Where-Object'] { $_ }) { + Write-EquivalenceResult -Difference "`$Actual has $($filteredKeysNotInExpected.Count) keys that were not found on `$Expected: $(Format-Nicely2 @($filteredKeysNotInExpected))." + } + else { + Write-EquivalenceResult "`$Actual has no keys that we did not find on `$Expected." + } + + foreach ($k in $filteredKeysNotInExpected | & $SafeCommands['Where-Object'] { $_ }) { + $result += "Expected is missing key '$k' that the other object has." + } + } + + if ($result | & $SafeCommands['Where-Object'] { $_ }) { + Write-EquivalenceResult -Difference "Hashtables `$Actual and `$Expected are not equivalent." + $expectedFormatted = Format-Nicely2 -Value $Expected + $actualFormatted = Format-Nicely2 -Value $Actual + return "Expected hashtable $expectedFormatted, but got $actualFormatted.`n$($result -join "`n")" + } + + Write-EquivalenceResult -Equivalence "Hastables `$Actual and `$Expected are equivalent." +} + +function Compare-DictionaryEquivalent ($Actual, $Expected, $Property, $Options) { + if (-not (Is-Dictionary -Value $Expected)) { + throw [ArgumentException]"Expected must be a dictionary." + } + + if (-not (Is-Dictionary -Value $Actual)) { + Write-EquivalenceResult -Difference "`$Actual is not a dictionary it is a $(Format-Nicely2 $Actual.GetType()), so they are not equivalent." + $expectedFormatted = Format-Nicely2 -Value $Expected + $actualFormatted = Format-Nicely2 -Value $Actual + return "Expected dictionary $expectedFormatted, but got $actualFormatted." + } + + # todo: if either side or both sides are empty dictionary make the verbose output shorter and nicer + + $actualKeys = $Actual.Keys + $expectedKeys = $Expected.Keys + + Write-EquivalenceResult "`Comparing all ($($expectedKeys.Count)) keys from `$Expected to keys in `$Actual." + $result = @() + foreach ($k in $expectedKeys) { + if (-not (Test-IncludedPath -PathSelector Hashtable -Path $Property -Options $Options -InputObject $k)) { + continue + } + + $actualHasKey = $actualKeys -contains $k + if (-not $actualHasKey) { + Write-EquivalenceResult -Difference "`$Actual is missing key '$k'." + $result += "Expected has key '$k' that the other object does not have." + continue + } + + $expectedValue = $Expected[$k] + $actualValue = $Actual[$k] + Write-EquivalenceResult "Both `$Actual and `$Expected have key '$k', comparing thier contents." + $result += Compare-Equivalent -Expected $expectedValue -Actual $actualValue -Path "$Property.$k" -Options $Options + } + if (!$Options.ExcludePathsNotOnExpected) { + # fix for powershell 2 where the array needs to be explicit + $keysNotInExpected = @( $actualKeys | & $SafeCommands['Where-Object'] { $expectedKeys -notcontains $_ } ) + $filteredKeysNotInExpected = @( $keysNotInExpected | Test-IncludedPath -PathSelector Hashtable -Path $Property -Options $Options ) + + # fix for powershell v2 where foreach goes once over null + if ($filteredKeysNotInExpected | & $SafeCommands['Where-Object'] { $_ }) { + Write-EquivalenceResult -Difference "`$Actual has $($filteredKeysNotInExpected.Count) keys that were not found on `$Expected: $(Format-Nicely2 @($filteredKeysNotInExpected))." + } + else { + Write-EquivalenceResult "`$Actual has no keys that we did not find on `$Expected." + } + + foreach ($k in $filteredKeysNotInExpected | & $SafeCommands['Where-Object'] { $_ }) { + $result += "Expected is missing key '$k' that the other object has." + } + } + + if ($result) { + Write-EquivalenceResult -Difference "Dictionaries `$Actual and `$Expected are not equivalent." + $expectedFormatted = Format-Nicely2 -Value $Expected + $actualFormatted = Format-Nicely2 -Value $Actual + return "Expected dictionary $expectedFormatted, but got $actualFormatted.`n$($result -join "`n")" + } + Write-EquivalenceResult -Equivalence "Dictionaries `$Actual and `$Expected are equivalent." +} + +function Compare-ObjectEquivalent ($Actual, $Expected, $Property, $Options) { + + if (-not (Is-Object -Value $Expected)) { + throw [ArgumentException]"Expected must be an object." + } + + if (-not (Is-Object -Value $Actual)) { + Write-EquivalenceResult -Difference "`$Actual is not an object it is a $(Format-Nicely2 $Actual.GetType()), so they are not equivalent." + $expectedFormatted = Format-Nicely2 -Value $Expected + $actualFormatted = Format-Nicely2 -Value $Actual + return "Expected object $expectedFormatted, but got $actualFormatted." + } + + $actualProperties = $Actual.PsObject.Properties + $expectedProperties = $Expected.PsObject.Properties + + Write-EquivalenceResult "Comparing ($(@($expectedProperties).Count)) properties of `$Expected to `$Actual." + foreach ($p in $expectedProperties) { + if (-not (Test-IncludedPath -PathSelector Property -InputObject $p -Options $Options -Path $Property)) { + continue + } + + $propertyName = $p.Name + $actualProperty = $actualProperties | & $SafeCommands['Where-Object'] { $_.Name -eq $propertyName } + if (-not $actualProperty) { + Write-EquivalenceResult -Difference "Property '$propertyName' was not found on `$Actual." + "Expected has property '$PropertyName' that the other object does not have." + continue + } + Write-EquivalenceResult "Property '$propertyName` was found on `$Actual, comparing them for equivalence." + $differences = Compare-Equivalent -Expected $p.Value -Actual $actualProperty.Value -Path "$Property.$propertyName" -Options $Options + if (-not $differences) { + Write-EquivalenceResult -Equivalence "Property '$propertyName` is equivalent." + } + else { + Write-EquivalenceResult -Difference "Property '$propertyName` is not equivalent." + } + $differences + } + + if (!$Options.ExcludePathsNotOnExpected) { + #check if there are any extra actual object props + $expectedPropertyNames = $expectedProperties | Select-Object -ExpandProperty Name + + $propertiesNotInExpected = @( $actualProperties | & $SafeCommands['Where-Object'] { $expectedPropertyNames -notcontains $_.name }) + + # fix for powershell v2 we need to make the array explicit + $filteredPropertiesNotInExpected = $propertiesNotInExpected | + Test-IncludedPath -PathSelector Property -Options $Options -Path $Property + + if ($filteredPropertiesNotInExpected) { + Write-EquivalenceResult -Difference "`$Actual has ($(@($filteredPropertiesNotInExpected).Count)) properties that `$Expected does not have: $(Format-Nicely2 @($filteredPropertiesNotInExpected))." + } + else { + Write-EquivalenceResult -Equivalence "`$Actual has no extra properties that `$Expected does not have." + } + + # fix for powershell v2 where foreach goes once over null + foreach ($p in $filteredPropertiesNotInExpected | & $SafeCommands['Where-Object'] { $_ }) { + "Expected is missing property '$($p.Name)' that the other object has." + } + } +} + +function Compare-DataRowEquivalent ($Actual, $Expected, $Property, $Options) { + + if (-not (Is-DataRow -Value $Expected)) { + throw [ArgumentException]"Expected must be a DataRow." + } + + if (-not (Is-DataRow -Value $Actual)) { + $expectedFormatted = Format-Nicely2 -Value $Expected + $actualFormatted = Format-Nicely2 -Value $Actual + return "Expected DataRow '$expectedFormatted', but got '$actualFormatted'." + } + + $actualProperties = $Actual.PsObject.Properties | & $SafeCommands['Where-Object'] { 'RowError', 'RowState', 'Table', 'ItemArray', 'HasErrors' -notcontains $_.Name } + $expectedProperties = $Expected.PsObject.Properties | & $SafeCommands['Where-Object'] { 'RowError', 'RowState', 'Table', 'ItemArray', 'HasErrors' -notcontains $_.Name } + + foreach ($p in $expectedProperties) { + $propertyName = $p.Name + $actualProperty = $actualProperties | & $SafeCommands['Where-Object'] { $_.Name -eq $propertyName } + if (-not $actualProperty) { + "Expected has property '$PropertyName' that the other object does not have." + continue + } + + Compare-Equivalent -Expected $p.Value -Actual $actualProperty.Value -Path "$Property.$propertyName" -Options $Options + } + + #check if there are any extra actual object props + $expectedPropertyNames = $expectedProperties | Select-Object -ExpandProperty Name + + $propertiesNotInExpected = @($actualProperties | & $SafeCommands['Where-Object'] { $expectedPropertyNames -notcontains $_.name }) + + # fix for powershell v2 where foreach goes once over null + foreach ($p in $propertiesNotInExpected | & $SafeCommands['Where-Object'] { $_ }) { + "Expected is missing property '$($p.Name)' that the other object has." + } +} + +function Write-EquivalenceResult { + [CmdletBinding()] + param( + [String] $String, + [Switch] $Difference, + [Switch] $Equivalence, + [Switch] $Skip + ) + + # we are using implict variable $Path + # from the parent scope, this is ugly + # and bad practice, but saves us ton of + # coding and boilerplate code + + $p = "" + $p += if ($null -ne $Path) { + "($Path)" + } + + $p += if ($Difference) { + " DIFFERENCE" + } + + $p += if ($Equivalence) { + " EQUIVALENCE" + } + + $p += if ($Skip) { + " SKIP" + } + + $p += if ("" -ne $p) { + " - " + } + + & $SafeCommands['Write-Verbose'] ("$p$String".Trim() + " ") +} + +# compares two objects for equivalency and returns $null when they are equivalent +# or a string message when they are not +function Compare-Equivalent { + [CmdletBinding()] + param( + $Actual, + $Expected, + $Path, + $Options = (& { + & $SafeCommands['Write-Warning'] "Getting default equivalency options, this should never be seen. If you see this and you are not developing Pester, please file issue at https://github.com/pester/Pester/issues" + Get-EquivalencyOption + }) + ) + + if ($null -ne $Options.ExludedPaths -and $Options.ExcludedPaths -contains $Path) { + Write-EquivalenceResult -Skip "Current path '$Path' is excluded from the comparison." + return + } + + # start by null checks to avoid implementing null handling + # logic in the functions that follow + if ($null -eq $Expected) { + Write-EquivalenceResult "`$Expected is `$null, so we are expecting `$null." + if ($Expected -ne $Actual) { + Write-EquivalenceResult -Difference "`$Actual is not equivalent to $(Format-Nicely2 $Expected), because it has a value of type $(Format-Nicely2 $Actual.GetType())." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Path -Options $Options + } + # we terminate here, either we passed the test and return nothing, or we did not + # and the previous statement returned message + Write-EquivalenceResult -Equivalence "`$Actual is equivalent to `$null, because it is `$null." + return + } + + if ($null -eq $Actual) { + Write-EquivalenceResult -Difference "`$Actual is $(Format-Nicely2), but `$Expected has value of type $(Format-Nicely2 $Expected.GetType()), so they are not equivalent." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Path + } + + Write-EquivalenceResult "`$Expected has type $(Format-Nicely2 $Expected.GetType()), `$Actual has type $(Format-Nicely2 $Actual.GetType()), they are both non-null." + + # test value types, strings, and single item arrays with values in them as values + # expand the single item array to get to the value in it + if (Is-Value -Value $Expected) { + Write-EquivalenceResult "`$Expected is a value (value type, string, single value array, or a scriptblock), we will be comparing `$Actual to value types." + Compare-ValueEquivalent -Actual $Actual -Expected $Expected -Property $Path -Options $Options + return + } + + # are the same instance + if (Test-Same -Expected $Expected -Actual $Actual) { + Write-EquivalenceResult -Equivalence "`$Expected and `$Actual are equivalent because they are the same object (by reference)." + return + } + + if (Is-Hashtable -Value $Expected) { + Write-EquivalenceResult "`$Expected is a hashtable, we will be comparing `$Actual to hashtables." + Compare-HashtableEquivalent -Expected $Expected -Actual $Actual -Property $Path -Options $Options + return + } + + # dictionaries? (they are IEnumerable so they must go before collections) + if (Is-Dictionary -Value $Expected) { + Write-EquivalenceResult "`$Expected is a dictionary, we will be comparing `$Actual to dictionaries." + Compare-DictionaryEquivalent -Expected $Expected -Actual $Actual -Property $Path -Options $Options + return + } + + #compare DataTable + if (Is-DataTable -Value $Expected) { + # todo add verbose output to data table + Write-EquivalenceResult "`$Expected is a datatable, we will be comparing `$Actual to datatables." + Compare-DataTableEquivalent -Expected $Expected -Actual $Actual -Property $Path -Options $Options + return + } + + #compare collection + if (Is-Collection -Value $Expected) { + Write-EquivalenceResult "`$Expected is a collection, we will be comparing `$Actual to collections." + Compare-CollectionEquivalent -Expected $Expected -Actual $Actual -Property $Path -Options $Options + return + } + + #compare DataRow + if (Is-DataRow -Value $Expected) { + # todo add verbose output to data row + Write-EquivalenceResult "`$Expected is a datarow, we will be comparing `$Actual to datarows." + Compare-DataRowEquivalent -Expected $Expected -Actual $Actual -Property $Path -Options $Options + return + } + + Write-EquivalenceResult "`$Expected is an object of type $(Format-Nicely2 $Expected.GetType()), we will be comparing `$Actual to objects." + Compare-ObjectEquivalent -Expected $Expected -Actual $Actual -Property $Path -Options $Options +} + +function Assert-Equivalent { + <# + .SYNOPSIS + Compares two objects for equivalency, by recursively comparing their properties for equivalency. + + .PARAMETER Actual + The actual object to compare. + + .PARAMETER Expected + The expected object to compare. + + .PARAMETER Because + The reason why the input should be the expected value. + + .PARAMETER Options + Options for the comparison. Get-EquivalencyOption function is called to get the default options. + + .PARAMETER StrictOrder + If set, the order of items in collections will be compared. + + .EXAMPLE + ```powershell + $expected = [PSCustomObject] @{ + Name = "Thomas" + } + + $actual = [PSCustomObject] @{ + Name = "Jakub" + Age = 30 + } + + $actual | Should-BeEquivalent $expected + ``` + + This will throw an error because the actual object has an additional property Age and the Name values are not equivalent. + + .EXAMPLE + ```powershell + $expected = [PSCustomObject] @{ + Name = "Thomas" + } + + $actual = [PSCustomObject] @{ + Name = "Thomas" + } + + $actual | Should-BeEquivalent $expected + ``` + + This will pass because the actual object has the same properties as the expected object and the Name values are equivalent. + + .LINK + https://pester.dev/docs/commands/Should-BeEquivalent + + .LINK + https://pester.dev/docs/assertions + #> + [CmdletBinding()] + param( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because, + $Options = (Get-EquivalencyOption) + # TODO: I am not sure this works. + # [Switch] $StrictOrder + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + $areDifferent = Compare-Equivalent -Actual $Actual -Expected $Expected -Options $Options | & $SafeCommands['Out-String'] + + if ($areDifferent) { + $optionsFormatted = Format-EquivalencyOptions -Options $Options + # the paremeter is -Option not -Options + $message = Get-AssertionMessage -Actual $actual -Expected $Expected -Option $optionsFormatted -Pretty -CustomMessage "Expected and actual are not equivalent!`nExpected:`n`n`nActual:`n`n`nSummary:`n$areDifferent`n" + throw [Pester.Factory]::CreateShouldErrorRecord($message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + Write-EquivalenceResult -Equivalence "`$Actual and `$Expected are equivalent." +} + +function Get-EquivalencyOption { + <# + .SYNOPSIS + Generates an object containing options for checking equivalency. + + .DESCRIPTION + The `Get-EquivalencyOption` function creates a custom object with options that determine how equivalency between two objects is assessed. This can be used in scenarios where a deep comparison of objects is required, with the ability to exclude specific paths from the comparison and to choose between different comparison strategies. + + .PARAMETER ExcludePath + An array of strings specifying the paths to exclude from the comparison. Each path should correspond to a property name or a chain of property names separated by dots for nested properties. + + .PARAMETER ExcludePathsNotOnExpected + A switch parameter that, when set, excludes any paths from the comparison that are not present on the expected object. This is useful for ignoring extra properties on the actual object that are not relevant to the comparison. + + .PARAMETER Comparator + Specifies the comparison strategy to use. The options are 'Equivalency' for a deep comparison that considers the structure and values of objects, and 'Equality' for a simple equality comparison. The default is 'Equivalency'. + + .EXAMPLE + $option = Get-EquivalencyOption -ExcludePath 'Id', 'Timestamp' -Comparator 'Equality' + This example generates an equivalency option object that excludes the 'Id' and 'Timestamp' properties from the comparison and uses a simple equality comparison strategy. + + .EXAMPLE + $option = Get-EquivalencyOption -ExcludePathsNotOnExpected + This example generates an equivalency option object that excludes any paths not present on the expected object from the comparison, using the default deep comparison strategy. + + .LINK + https://pester.dev/docs/commands/Get-EquivalencyOption + + .LINK + https://pester.dev/docs/assertions + #> + + [CmdletBinding()] + param( + [string[]] $ExcludePath = @(), + [switch] $ExcludePathsNotOnExpected, + [ValidateSet('Equivalency', 'Equality')] + [string] $Comparator = 'Equivalency' + ) + + [PSCustomObject]@{ + ExcludedPaths = [string[]] $ExcludePath + ExcludePathsNotOnExpected = [bool] $ExcludePathsNotOnExpected + Comparator = [string] $Comparator + } +} + +function Test-IncludedPath { + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + $InputObject, + [String] + $Path, + $Options, + [Parameter(Mandatory = $true)] + [ValidateSet("Property", "Hashtable")] + $PathSelector + ) + + begin { + $selector = switch ($PathSelector) { + "Property" { { param($InputObject) $InputObject.Name } } + "Hashtable" { { param($InputObject) $InputObject } } + Default { throw "Unsupported path selector." } + } + } + + process { + if ($null -eq $Options.ExcludedPaths) { + return $InputObject + } + + $subPath = &$selector $InputObject + $fullPath = "$Path.$subPath".Trim('.') + + + if ($fullPath | Like-Any $Options.ExcludedPaths) { + Write-EquivalenceResult -Skip "Current path $fullPath is excluded from the comparison." + } + else { + $InputObject + } + } +} + +function Format-EquivalencyOptions ($Options) { + $Options.ExcludedPaths | & $SafeCommands['ForEach-Object'] { "Exclude path '$_'" } + if ($Options.ExcludePathsNotOnExpected) { "Excluding all paths not found on Expected" } +} + +function Like-Any { + param( + [String[]] $PathFilters, + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [String] $Path + ) + process { + foreach ($pathFilter in $PathFilters | & $SafeCommands['Where-Object'] { $_ }) { + $r = $Path -like $pathFilter + if ($r) { + Write-EquivalenceResult -Skip "Path '$Path' matches filter '$pathFilter'." + return $true + } + } + + return $false + } +} diff --git a/src/functions/assert/Exception/Should-Throw.ps1 b/src/functions/assert/Exception/Should-Throw.ps1 new file mode 100644 index 000000000..a4b564a58 --- /dev/null +++ b/src/functions/assert/Exception/Should-Throw.ps1 @@ -0,0 +1,162 @@ +function Assert-Throw { + <# + .SYNOPSIS + Asserts that a script block throws an exception. + + .PARAMETER ScriptBlock + The script block that should throw an exception. + + .PARAMETER ExceptionType + The type of exception that should be thrown. + + .PARAMETER ExceptionMessage + The message that the exception should contain. `-like` wildcards are supported. + + .PARAMETER FullyQualifiedErrorId + The FullyQualifiedErrorId that the exception should contain. `-like` wildcards are supported. + + .PARAMETER AllowNonTerminatingError + If set, the assertion will pass if a non-terminating error is thrown. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + { throw 'error' } | Should-Throw + { throw 'error' } | Should-Throw -ExceptionMessage 'error' + { throw 'error' } | Should-Throw -ExceptionType 'System.Management.Automation.RuntimeException' + { throw 'error' } | Should-Throw -FullyQualifiedErrorId 'RuntimeException' + { throw 'error' } | Should-Throw -FullyQualifiedErrorId '*Exception' + { throw 'error' } | Should-Throw -AllowNonTerminatingError + ``` + + All of these assertions will pass. + + .EXAMPLE + ```powershell + $err = { throw 'error' } | Should-Throw + $err.Exception.Message | Should-BeLike '*err*' + ``` + + The error record is returned from the assertion and can be used in further assertions. + + .LINK + https://pester.dev/docs/commands/Should-Throw + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(ValueFromPipeline = $true, Mandatory = $true)] + [ScriptBlock]$ScriptBlock, + [Type]$ExceptionType, + [String]$ExceptionMessage, + [String]$FullyQualifiedErrorId, + [Switch]$AllowNonTerminatingError, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $ScriptBlock -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $ScriptBlock = $collectedInput.Actual + + $errorThrown = $false + $err = $null + try { + $p = 'stop' + if ($AllowNonTerminatingError) { + $p = 'continue' + } + + $eap = [PSVariable]::new("erroractionpreference", $p) + $null = $ScriptBlock.InvokeWithContext($null, $eap, $null) 2>&1 + } + catch { + $errorThrown = $true + $err = Get-ErrorObject $_ + } + + $buts = @() + $filters = @() + + $filterOnExceptionType = $null -ne $ExceptionType + if ($filterOnExceptionType) { + $exceptionFilterTypeFormatted = Format-Type2 $ExceptionType + + $filters += "of type $exceptionFilterTypeFormatted" + + $exceptionTypeFilterMatches = $err.Exception -is $ExceptionType + if (-not $exceptionTypeFilterMatches) { + $exceptionTypeFormatted = Get-ShortType2 $err.Exception + $buts += "the exception type was $exceptionTypeFormatted" + } + } + + $filterOnMessage = -not ([string]::IsNullOrWhiteSpace($ExceptionMessage)) + if ($filterOnMessage) { + $filters += "with message '$ExceptionMessage'" + if ($err.ExceptionMessage -notlike $ExceptionMessage) { + $buts += "the message was '$($err.ExceptionMessage)'" + } + } + + $filterOnId = -not ([string]::IsNullOrWhiteSpace($FullyQualifiedErrorId)) + if ($filterOnId) { + $filters += "with FullyQualifiedErrorId '$FullyQualifiedErrorId'" + if ($err.FullyQualifiedErrorId -notlike $FullyQualifiedErrorId) { + $buts += "the FullyQualifiedErrorId was '$($err.FullyQualifiedErrorId)'" + } + } + + if (-not $errorThrown) { + $buts += "no exception was thrown" + } + + if ($buts.Count -ne 0) { + $filter = Add-SpaceToNonEmptyString ( Join-And $filters -Threshold 3 ) + $but = Join-And $buts + $defaultMessage = "Expected an exception,$filter to be thrown, but $but." + + $Message = Get-AssertionMessage -Expected $Expected -Actual $ScriptBlock -Because $Because ` + -DefaultMessage $defaultMessage + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $err.ErrorRecord +} + +function Get-ErrorObject ($ErrorRecord) { + + if ($ErrorRecord.Exception -like '*"InvokeWithContext"*') { + $e = $ErrorRecord.Exception.InnerException.ErrorRecord + } + else { + $e = $ErrorRecord + } + [PSCustomObject] @{ + ErrorRecord = $e + ExceptionMessage = $e.Exception.Message + Exception = $e.Exception + ExceptionType = $e.Exception.GetType() + FullyQualifiedErrorId = $e.FullyQualifiedErrorId + } +} + +function Join-And ($Items, $Threshold = 2) { + + if ($null -eq $items -or $items.count -lt $Threshold) { + $items -join ', ' + } + else { + $c = $items.count + ($items[0..($c - 2)] -join ', ') + ' and ' + $items[-1] + } +} + +function Add-SpaceToNonEmptyString ([string]$Value) { + if ($Value) { + " $Value" + } +} diff --git a/src/functions/assert/General/Should-Be.ps1 b/src/functions/assert/General/Should-Be.ps1 new file mode 100644 index 000000000..4537aad70 --- /dev/null +++ b/src/functions/assert/General/Should-Be.ps1 @@ -0,0 +1,49 @@ +function Assert-Equal { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if they are equal. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1 | Should-Be 1 + "hello" | Should-Be "hello" + ``` + + These assertions will pass, because the expected value is equal to the actual value. + + .LINK + https://pester.dev/docs/commands/Should-Be + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [AllowNull()] + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ((Ensure-ExpectedIsNotCollection $Expected) -ne $Actual) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected , but got ." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-BeGreaterThan.ps1 b/src/functions/assert/General/Should-BeGreaterThan.ps1 new file mode 100644 index 000000000..cf2728009 --- /dev/null +++ b/src/functions/assert/General/Should-BeGreaterThan.ps1 @@ -0,0 +1,47 @@ +function Assert-GreaterThan { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if the actual value is greater than the expected value. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 2 | Should-BeGreaterThan 1 + 2 | Should-BeGreaterThan 2 + ``` + + These assertions will pass, because the actual value is greater than the expected value. + + .LINK + https://pester.dev/docs/commands/Should-BeGreaterThan + + .LINK + https://pester.dev/docs/assertions + #> + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ((Ensure-ExpectedIsNotCollection $Expected) -ge $Actual) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected the actual value to be greater than , but it was not. Actual: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-BeGreaterThanOrEqual.ps1 b/src/functions/assert/General/Should-BeGreaterThanOrEqual.ps1 new file mode 100644 index 000000000..c528c5b5c --- /dev/null +++ b/src/functions/assert/General/Should-BeGreaterThanOrEqual.ps1 @@ -0,0 +1,47 @@ +function Assert-GreaterThanOrEqual { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if the actual value is greater than or equal to the expected value. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 2 | Should-BeGreaterThanOrEqual 1 + 2 | Should-BeGreaterThanOrEqual 2 + ``` + + These assertions will pass, because the actual value is greater than or equal to the expected value. + + .LINK + https://pester.dev/docs/commands/Should-BeGreaterThanOrEqual + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ((Ensure-ExpectedIsNotCollection $Expected) -gt $Actual) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected the actual value to be greater than or equal to , but it was not. Actual: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-BeLessThan.ps1 b/src/functions/assert/General/Should-BeLessThan.ps1 new file mode 100644 index 000000000..17fc6c926 --- /dev/null +++ b/src/functions/assert/General/Should-BeLessThan.ps1 @@ -0,0 +1,47 @@ +function Assert-LessThan { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if the actual value is less than the expected value. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1 | Should-BeLessThan 2 + 0 | Should-BeLessThan 1 + ``` + + These assertions will pass, because the actual value is less than the expected value. + + .LINK + https://pester.dev/docs/commands/Should-BeLessThan + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ((Ensure-ExpectedIsNotCollection $Expected) -le $Actual) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected the actual value to be less than , but it was not. Actual: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-BeLessThanOrEqual.ps1 b/src/functions/assert/General/Should-BeLessThanOrEqual.ps1 new file mode 100644 index 000000000..69ed80355 --- /dev/null +++ b/src/functions/assert/General/Should-BeLessThanOrEqual.ps1 @@ -0,0 +1,56 @@ +function Assert-LessThanOrEqual { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if the actual value is less than or equal to the expected value. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + 1 | Should-BeLessThanOrEqual 2 + 1 | Should-BeLessThanOrEqual 1 + ``` + + These assertions will pass, because the actual value is less than or equal to the expected value. + + .EXAMPLE + ```powershell + 2 | Should-BeLessThanOrEqual 1 + ``` + + This assertion will fail, because the actual value is not less than or equal to the expected value. + + .NOTES + The `Should-BeLessThanOrEqual` assertion is the opposite of the `Should-BeGreaterThan` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeLessThanOrEqual + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ((Ensure-ExpectedIsNotCollection $Expected) -lt $Actual) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected the actual value to be less than or equal to , but it was not. Actual: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-BeNull.ps1 b/src/functions/assert/General/Should-BeNull.ps1 new file mode 100644 index 000000000..2d4145ca1 --- /dev/null +++ b/src/functions/assert/General/Should-BeNull.ps1 @@ -0,0 +1,41 @@ +function Assert-Null { + <# + .SYNOPSIS + Asserts that the input is `$null`. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be `$null`. + + .EXAMPLE + ```powershell + $null | Should-BeNull + ``` + + This assertion will pass, because the actual value is `$null`. + + .LINK + https://pester.dev/docs/commands/Should-BeNull + + .LINK + https://pester.dev/docs/assertions + #> + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ($null -ne $Actual) { + $Message = Get-AssertionMessage -Expected $null -Actual $Actual -Because $Because -DefaultMessage "Expected `$null, but got ''." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-BeSame.ps1 b/src/functions/assert/General/Should-BeSame.ps1 new file mode 100644 index 000000000..ee3193f5a --- /dev/null +++ b/src/functions/assert/General/Should-BeSame.ps1 @@ -0,0 +1,62 @@ +function Assert-Same { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if they are the same instance. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be the expected value. + + .EXAMPLE + ```powershell + $a = New-Object Object + $a | Should-BeSame $a + ``` + + This assertion will pass, because the actual value is the same instance as the expected value. + + .EXAMPLE + ```powershell + $a = New-Object Object + $b = New-Object Object + $a | Should-BeSame $b + ``` + + This assertion will fail, because the actual value is not the same instance as the expected value. + + .NOTES + The `Should-BeSame` assertion is the opposite of the `Should-NotBeSame` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeSame + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + if ($Expected -is [ValueType] -or $Expected -is [string]) { + throw [ArgumentException]"Should-BeSame compares objects by reference. You provided a value type or a string, those are not reference types and you most likely don't need to compare them by reference, see https://github.com/nohwnd/Assert/issues/6.`n`nAre you trying to compare two values to see if they are equal? Use Should-BeEqual instead." + } + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if (-not ([object]::ReferenceEquals($Expected, $Actual))) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected , to be the same instance but it was not. Actual: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-HaveType.ps1 b/src/functions/assert/General/Should-HaveType.ps1 new file mode 100644 index 000000000..1ec6a24a9 --- /dev/null +++ b/src/functions/assert/General/Should-HaveType.ps1 @@ -0,0 +1,47 @@ +function Assert-Type { + <# + .SYNOPSIS + Asserts that the input is of the expected type. + + .PARAMETER Expected + The expected type. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should be the expected type. + + .EXAMPLE + ```powershell + "hello" | Should-HaveType ([String]) + 1 | Should-HaveType ([Int32]) + ``` + + These assertions will pass, because the actual value is of the expected type. + + .LINK + https://pester.dev/docs/commands/Should-HaveType + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + [Type]$Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ($Actual -isnot $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected value to have type , but got ." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-NotBe.ps1 b/src/functions/assert/General/Should-NotBe.ps1 new file mode 100644 index 000000000..59700808b --- /dev/null +++ b/src/functions/assert/General/Should-NotBe.ps1 @@ -0,0 +1,48 @@ +function Assert-NotEqual { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if they are not equal. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should not be the expected value. + + .EXAMPLE + ```powershell + 1 | Should-NotBe 2 + "hello" | Should-NotBe "world" + ``` + + These assertions will pass, because the actual value is not equal to the expected value. + + .LINK + https://pester.dev/docs/commands/Should-NotBe + + .LINK + https://pester.dev/docs/assertions + + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [AllowNull()] + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ((Ensure-ExpectedIsNotCollection $Expected) -eq $Actual) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected , to be different than the actual value, but they were equal." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-NotBeNull.ps1 b/src/functions/assert/General/Should-NotBeNull.ps1 new file mode 100644 index 000000000..8387fca23 --- /dev/null +++ b/src/functions/assert/General/Should-NotBeNull.ps1 @@ -0,0 +1,41 @@ +function Assert-NotNull { + <# + .SYNOPSIS + Asserts that the input is not `$null`. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should not be `$null`. + + .EXAMPLE + ```powershell + "hello" | Should-NotBeNull + 1 | Should-NotBeNull + ``` + + These assertions will pass, because the actual value is not `$null. + + .LINK + https://pester.dev/docs/commands/Should-NotBeNull + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ($null -eq $Actual) { + $Message = Get-AssertionMessage -Expected $null -Actual $Actual -Because $Because -DefaultMessage "Expected not `$null, but got `$null." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-NotBeSame.ps1 b/src/functions/assert/General/Should-NotBeSame.ps1 new file mode 100644 index 000000000..056b6d83c --- /dev/null +++ b/src/functions/assert/General/Should-NotBeSame.ps1 @@ -0,0 +1,56 @@ +function Assert-NotSame { + <# + .SYNOPSIS + Compares the expected value to actual value, to see if the actual value is not the same instance as the expected value. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should not be the expected value. + + .EXAMPLE + ```powershell + $object = New-Object -TypeName PSObject + $object | Should-NotBeSame $object + ``` + This assertion will pass, because the actual value is not the same instance as the expected value. + + .EXAMPLE + ```powershell + $object = New-Object -TypeName PSObject + $object | Should-NotBeSame $object + ``` + + This assertion will fail, because the actual value is the same instance as the expected value. + + .NOTES + The `Should-NotBeSame` assertion is the opposite of the `Should-BeSame` assertion. + + .LINK + https://pester.dev/docs/commands/Should-NotBeSame + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + $Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ([object]::ReferenceEquals($Expected, $Actual)) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected , to not be the same instance, but they were the same instance." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/General/Should-NotHaveType.ps1 b/src/functions/assert/General/Should-NotHaveType.ps1 new file mode 100644 index 000000000..762397371 --- /dev/null +++ b/src/functions/assert/General/Should-NotHaveType.ps1 @@ -0,0 +1,49 @@ +function Assert-NotType { + <# + .SYNOPSIS + Asserts that the input is not of the expected type. + + .PARAMETER Expected + The expected type. + + .PARAMETER Actual + The actual value. + + .PARAMETER Because + The reason why the input should not be the expected type. + + .EXAMPLE + ```powershell + "hello" | Should-NotHaveType ([Int32]) + 1 | Should-NotHaveType ([String]) + ``` + + These assertions will pass, because the actual value is not of the expected type. + + .NOTES + This assertion is the opposite of `Should-HaveType`. + + .LINK + https://pester.dev/docs/commands/Should-NotHaveType + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + [Type]$Expected, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + if ($Actual -is $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected value to be of different type than , but got ." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + + $Actual +} diff --git a/src/functions/assert/String/Should-BeEmptyString.ps1 b/src/functions/assert/String/Should-BeEmptyString.ps1 new file mode 100644 index 000000000..3a53dc2d1 --- /dev/null +++ b/src/functions/assert/String/Should-BeEmptyString.ps1 @@ -0,0 +1,58 @@ +function Assert-StringEmpty { + <# + .SYNOPSIS + Ensures that input is an empty string. + + .PARAMETER Actual + The actual value that will be compared to an empty string. + + .PARAMETER Because + The reason why the input should be an empty string. + + .EXAMPLE + ```powershell + $actual = "" + $actual | Should-BeEmptyString + ``` + + This test will pass. + + .EXAMPLE + ```powershell + $actual = "hello" + $actual | Should-BeEmptyString + ``` + + This test will fail, the input is not an empty string. + + .EXAMPLE + ``` + $null | Should-BeEmptyString + @() | Should-BeEmptyString + $() | Should-BeEmptyString + $false | Should-BeEmptyString + ``` + + All the tests above will fail, the input is not a string. + + .LINK + https://pester.dev/docs/commands/Should-BeEmptyString + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 0, ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -isnot [String] -or -not [String]::IsNullOrEmpty( $Actual)) { + $formattedMessage = Get-AssertionMessage -Actual $Actual -Because $Because -DefaultMessage "Expected a [string] that is empty, but got : " -Pretty + throw [Pester.Factory]::CreateShouldErrorRecord($formattedMessage, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/String/Should-BeLikeString.ps1 b/src/functions/assert/String/Should-BeLikeString.ps1 new file mode 100644 index 000000000..0ae136a08 --- /dev/null +++ b/src/functions/assert/String/Should-BeLikeString.ps1 @@ -0,0 +1,87 @@ +function Test-Like { + param ( + [String]$Expected, + $Actual, + [switch]$CaseSensitive + ) + + if (-not $CaseSensitive) { + $Actual -like $Expected + } + else { + $Actual -clike $Expected + } +} + +function Assert-Like { + <# + .SYNOPSIS + Asserts that the actual value is like the expected value. + + .DESCRIPTION + The `Should-BeLikeString` assertion compares the actual value to the expected value using the `-like` operator. The `-like` operator is case-insensitive by default, but you can make it case-sensitive by using the `-CaseSensitive` switch. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER CaseSensitive + Indicates that the comparison should be case-sensitive. + + .PARAMETER Because + The reason why the actual value should be like the expected value. + + .EXAMPLE + ```powershell + "hello" | Should-BeLikeString "h*" + ``` + + This assertion will pass, because the actual value is like the expected value. + + .EXAMPLE + ```powershell + + "hello" | Should-BeLikeString "H*" -CaseSensitive + ``` + + This assertion will fail, because the actual value is not like the expected value. + + .NOTES + The `Should-BeLikeString` assertion is the opposite of the `Should-NotBeLikeString` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeLikeString + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory = $true)] + [String]$Expected, + [Switch]$CaseSensitive, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -isnot [string]) { + throw [ArgumentException]"Actual is expected to be string, to avoid confusing behavior that -like operator exhibits with collections. To assert on collections use Should-Any, Should-All or some other collection assertion." + } + + $stringsAreAlike = Test-Like -Expected $Expected -Actual $Actual -CaseSensitive:$CaseSensitive -IgnoreWhitespace:$IgnoreWhiteSpace + if (-not ($stringsAreAlike)) { + $caseSensitiveMessage = "" + if ($CaseSensitive) { + $caseSensitiveMessage = " case sensitively" + } + + $Message = Get-AssertionMessage -Expected $null -Actual $Actual -Because $Because -DefaultMessage "Expected the string '$Actual' to$caseSensitiveMessage be like '$Expected', but it did not." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/String/Should-BeString.ps1 b/src/functions/assert/String/Should-BeString.ps1 new file mode 100644 index 000000000..3471c99b5 --- /dev/null +++ b/src/functions/assert/String/Should-BeString.ps1 @@ -0,0 +1,91 @@ +function Test-StringEqual { + param ( + [String]$Expected, + $Actual, + [switch]$CaseSensitive, + [switch]$IgnoreWhitespace + ) + + if ($Actual -isnot [string]) { + return $false + } + + if ($IgnoreWhitespace) { + $Expected = $Expected -replace '\s' + $Actual = $Actual -replace '\s' + } + + if (-not $CaseSensitive) { + $Expected -eq $Actual + } + else { + $Expected -ceq $Actual + } +} + +function Assert-StringEqual { + <# + .SYNOPSIS + Asserts that the actual value is equal to the expected value. + + .DESCRIPTION + The `Should-BeString` assertion compares the actual value to the expected value using the `-eq` operator. The `-eq` operator is case-insensitive by default, but you can make it case-sensitive by using the `-CaseSensitive` switch. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER CaseSensitive + Indicates that the comparison should be case-sensitive. + + .PARAMETER IgnoreWhitespace + Indicates that the comparison should ignore whitespace. + + .PARAMETER Because + The reason why the actual value should be equal to the expected value. + + .EXAMPLE + ```powershell + "hello" | Should-BeString "hello" + ``` + + This assertion will pass, because the actual value is equal to the expected value. + + .EXAMPLE + ```powershell + "hello" | Should-BeString "HELLO" -CaseSensitive + ``` + + This assertion will fail, because the actual value is not equal to the expected value. + + .NOTES + The `Should-BeString` assertion is the opposite of the `Should-NotBeString` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeString + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory)] + [String]$Expected, + [String]$Because, + [switch]$CaseSensitive, + [switch]$IgnoreWhitespace + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + $stringsAreEqual = Test-StringEqual -Expected $Expected -Actual $Actual -CaseSensitive:$CaseSensitive -IgnoreWhitespace:$IgnoreWhiteSpace + if (-not ($stringsAreEqual)) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected , but got ." + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/String/Should-NotBeEmptyString.ps1 b/src/functions/assert/String/Should-NotBeEmptyString.ps1 new file mode 100644 index 000000000..3d6352ce2 --- /dev/null +++ b/src/functions/assert/String/Should-NotBeEmptyString.ps1 @@ -0,0 +1,58 @@ +function Assert-StringNotEmpty { + <# + .SYNOPSIS + Ensures that the input is a string, and that the input is not $null or empty string. + + .PARAMETER Actual + The actual value that will be compared. + + .PARAMETER Because + The reason why the input should be a string that is not $null or empty. + + .EXAMPLE + ```powershell + $actual = "hello" + $actual | Should-NotBeNullOrEmptyString + ``` + + This test will pass. + + .EXAMPLE + ```powershell + $actual = "" + $actual | Should-NotBeNullOrEmptyString + ``` + + This test will fail, the input is an empty string. + + .EXAMPLE + ``` + $null | Should-NotBeNullOrEmptyString + $() | Should-NotBeNullOrEmptyString + $false | Should-NotBeNullOrEmptyString + 1 | Should-NotBeNullOrEmptyString + ``` + + All the tests above will fail, the input is not a string. + + .LINK + https://pester.dev/docs/commands/Should-NotBeNullOrEmptyString + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 0, ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -isnot [String] -or [String]::IsNullOrEmpty($Actual)) { + $formattedMessage = Get-AssertionMessage -Actual $Actual -Because $Because -DefaultMessage "Expected a [string] that is not `$null or empty, but got : " -Pretty + throw [Pester.Factory]::CreateShouldErrorRecord($formattedMessage, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/String/Should-NotBeLikeString.ps1 b/src/functions/assert/String/Should-NotBeLikeString.ps1 new file mode 100644 index 000000000..d66c11992 --- /dev/null +++ b/src/functions/assert/String/Should-NotBeLikeString.ps1 @@ -0,0 +1,95 @@ +function Test-NotLike { + param ( + [String]$Expected, + $Actual, + [switch]$CaseSensitive + ) + + if (-not $CaseSensitive) { + $Actual -NotLike $Expected + } + else { + $actual -cNotLike $Expected + } +} + +function Get-NotLikeDefaultFailureMessage ([String]$Expected, $Actual, [switch]$CaseSensitive) { + $caseSensitiveMessage = "" + if ($CaseSensitive) { + $caseSensitiveMessage = " case sensitively" + } + "Expected the string '$Actual' to$caseSensitiveMessage not match '$Expected' but it matched it." +} + +function Assert-NotLike { + <# + .SYNOPSIS + Asserts that the actual value is not like the expected value. + + .DESCRIPTION + The `Should-NotBeLikeString` assertion compares the actual value to the expected value using the `-notlike` operator. The `-notlike` operator is case-insensitive by default, but you can make it case-sensitive by using the `-CaseSensitive` switch. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER CaseSensitive + Indicates that the comparison should be case-sensitive. + + .PARAMETER Because + The reason why the actual value should not be like the expected value. + + .EXAMPLE + ```powershell + "hello" | Should-NotBeLikeString "H*" + ``` + + This assertion will pass, because the actual value is not like the expected value. + + .EXAMPLE + ```powershell + "hello" | Should-NotBeLikeString "h*" -CaseSensitive + ``` + + This assertion will fail, because the actual value is like the expected value. + + .NOTES + The `Should-NotBeLikeString` assertion is the opposite of the `Should-BeLikeString` assertion. + + .LINK + https://pester.dev/docs/commands/Should-NotBeLikeString + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0, Mandatory = $true)] + [String]$Expected, + [Switch]$CaseSensitive, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -isnot [string]) { + throw [ArgumentException]"Actual is expected to be string, to avoid confusing behavior that -like operator exhibits with collections. To assert on collections use Should-Any, Should-All or some other collection assertion." + } + + $stringsAreANotLike = Test-NotLike -Expected $Expected -Actual $Actual -CaseSensitive:$CaseSensitive -IgnoreWhitespace:$IgnoreWhiteSpace + if (-not ($stringsAreANotLike)) { + if (-not $CustomMessage) { + $formattedMessage = Get-NotLikeDefaultFailureMessage -Expected $Expected -Actual $Actual -CaseSensitive:$CaseSensitive + } + else { + $formattedMessage = Get-CustomFailureMessage -Expected $Expected -Actual $Actual -Because $Because -CaseSensitive:$CaseSensitive + } + + throw [Pester.Factory]::CreateShouldErrorRecord($formattedMessage, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/String/Should-NotBeString.ps1 b/src/functions/assert/String/Should-NotBeString.ps1 new file mode 100644 index 000000000..6cc0a2019 --- /dev/null +++ b/src/functions/assert/String/Should-NotBeString.ps1 @@ -0,0 +1,70 @@ +function Get-StringNotEqualDefaultFailureMessage ([String]$Expected, $Actual) { + "Expected the strings to be different but they were the same '$Expected'." +} + +function Assert-StringNotEqual { + <# + .SYNOPSIS + Asserts that the actual value is not equal to the expected value. + + .DESCRIPTION + The `Should-NotBeString` assertion compares the actual value to the expected value using the `-ne` operator. The `-ne` operator is case-insensitive by default, but you can make it case-sensitive by using the `-CaseSensitive` switch. + + .PARAMETER Expected + The expected value. + + .PARAMETER Actual + The actual value. + + .PARAMETER CaseSensitive + Indicates that the comparison should be case-sensitive. + + .PARAMETER IgnoreWhitespace + Indicates that the comparison should ignore whitespace. + + .PARAMETER Because + The reason why the actual value should not be equal to the expected value. + + .EXAMPLE + ```powershell + "hello" | Should-NotBeString "HELLO" + ``` + This assertion will pass, because the actual value is not equal to the expected value. + + .EXAMPLE + ```powershell + "hello" | Should-NotBeString "hello" -CaseSensitive + ``` + This assertion will fail, because the actual value is equal to the expected value. + + .NOTES + The `Should-NotBeString` assertion is the opposite of the `Should-BeString` assertion. + + .LINK + https://pester.dev/docs/commands/Should-NotBeString + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0)] + [String]$Expected, + [String]$Because, + [switch]$CaseSensitive, + [switch]$IgnoreWhitespace + ) + + if (Test-StringEqual -Expected $Expected -Actual $Actual -CaseSensitive:$CaseSensitive -IgnoreWhitespace:$IgnoreWhiteSpace) { + if (-not $CustomMessage) { + $formattedMessage = Get-StringNotEqualDefaultFailureMessage -Expected $Expected -Actual $Actual + } + else { + $formattedMessage = Get-CustomFailureMessage -Expected $Expected -Actual $Actual -Because $Because + } + + throw [Pester.Factory]::CreateShouldErrorRecord($formattedMessage, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/String/Should-NotBeWhiteSpaceString.ps1 b/src/functions/assert/String/Should-NotBeWhiteSpaceString.ps1 new file mode 100644 index 000000000..c75ee0548 --- /dev/null +++ b/src/functions/assert/String/Should-NotBeWhiteSpaceString.ps1 @@ -0,0 +1,59 @@ +function Assert-StringNotWhiteSpace { + <# + .SYNOPSIS + Ensures that the input is a string, and that the input is not $null, empty, or whitespace only string. + + .PARAMETER Actual + The actual value that will be compared. + + .PARAMETER Because + The reason why the input should be a string that is not $null, empty, or whitespace only string. + + .EXAMPLE + ```powershell + $actual = "hello" + $actual | Should-NotBeNullOrWhiteSpaceString + ``` + + This test will pass. + + .EXAMPLE + ```powershell + $actual = " " + $actual | Should-NotBeNullOrWhiteSpaceString + ``` + + This test will fail, the input is a whitespace only string. + + .EXAMPLE + ``` + $null | Should-NotBeNullOrWhiteSpaceString + "" | Should-NotBeNullOrWhiteSpaceString + $() | Should-NotBeNullOrWhiteSpaceString + $false | Should-NotBeNullOrWhiteSpaceString + 1 | Should-NotBeNullOrWhiteSpaceString + ``` + + All the tests above will fail, the input is not a string. + + .LINK + https://pester.dev/docs/commands/Should-NotBeNullOrWhiteSpaceString + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 0, ValueFromPipeline = $true)] + $Actual, + [String]$Because + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -isnot [string] -or [string]::IsNullOrWhiteSpace($Actual)) { + $formattedMessage = Get-AssertionMessage -Actual $Actual -Because $Because -DefaultMessage "Expected a [string] that is not `$null, empty or whitespace, but got : " -Pretty + throw [Pester.Factory]::CreateShouldErrorRecord($formattedMessage, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/Time/Get-TimeSpanFromStringWithUnit.ps1 b/src/functions/assert/Time/Get-TimeSpanFromStringWithUnit.ps1 new file mode 100644 index 000000000..be64c95fe --- /dev/null +++ b/src/functions/assert/Time/Get-TimeSpanFromStringWithUnit.ps1 @@ -0,0 +1,18 @@ +function Get-TimeSpanFromStringWithUnit ([string] $Value) { + if ($Value -notmatch "(?^\d+(?:\.\d+)?)\s*(?ms|mil|m|h|d|s|w)") { + throw "String '$Value' is not a valid timespan string. It should be a number followed by a unit in short or long format (e.g. '1ms', '1s', '1m', '1h', '1d', '1w', '1sec', '1second', '1.5hours' etc.)." + } + + $suffix = $Matches['suffix'] + $valueFromRegex = $Matches['value'] + switch ($suffix) { + ms { [timespan]::FromMilliseconds($valueFromRegex) } + mil { [timespan]::FromMilliseconds($valueFromRegex) } + s { [timespan]::FromSeconds($valueFromRegex) } + m { [timespan]::FromMinutes($valueFromRegex) } + h { [timespan]::FromHours($valueFromRegex) } + d { [timespan]::FromDays($valueFromRegex) } + w { [timespan]::FromDays([double]$valueFromRegex * 7) } + default { throw "Time unit '$suffix' in '$Value' is not supported." } + } +} diff --git a/src/functions/assert/Time/Should-BeAfter.ps1 b/src/functions/assert/Time/Should-BeAfter.ps1 new file mode 100644 index 000000000..9e456a8da --- /dev/null +++ b/src/functions/assert/Time/Should-BeAfter.ps1 @@ -0,0 +1,114 @@ +function Assert-After { + <# + .SYNOPSIS + Asserts that the provided [datetime] is after the expected [datetime]. + + .PARAMETER Actual + The actual [datetime] value. + + .PARAMETER Expected + The expected [datetime] value. + + .PARAMETER Time + The time to add or subtract from the current time. This parameter uses fluent time syntax e.g. 1minute. + + .PARAMETER Ago + Indicates that the -Time should be subtracted from the current time. + + .PARAMETER FromNow + Indicates that the -Time should be added to the current time. + + .PARAMETER Now + Indicates that the current time should be used as the expected time. + + .PARAMETER Because + The reason why the actual value should be after the expected value. + + .EXAMPLE + ```powershell + (Get-Date).AddDays(1) | Should-BeAfter (Get-Date) + ``` + + + This assertion will pass, because the actual value is after the expected value. + + .EXAMPLE + ```powershell + (Get-Date).AddDays(-1) | Should-BeAfter (Get-Date) + ``` + + This assertion will fail, because the actual value is not after the expected value. + + .EXAMPLE + ```powershell + (Get-Date).AddDays(1) | Should-BeAfter 10minutes -FromNow + ``` + + This assertion will pass, because the actual value is after the expected value. + + .EXAMPLE + ```powershell + (Get-Date).AddDays(-1) | Should-BeAfter -Time 3days -Ago + ``` + + This assertion will pass, because the actual value is after the expected value. + + .NOTES + The `Should-BeAfter` assertion is the opposite of the `Should-BeBefore` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeAfter + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + [CmdletBinding(DefaultParameterSetName = "Now")] + param ( + [Parameter(Position = 2, ValueFromPipeline = $true)] + $Actual, + + [Parameter(Position = 0, ParameterSetName = "Now")] + [switch] $Now, + + [Parameter(Position = 0, ParameterSetName = "Fluent")] + $Time, + + [Parameter(Position = 1, ParameterSetName = "Fluent")] + [switch] $Ago, + + [Parameter(Position = 1, ParameterSetName = "Fluent")] + [switch] $FromNow, + + [Parameter(Position = 0, ParameterSetName = "Expected")] + [DateTime] $Expected + ) + + # Now is just a syntax marker, we don't need to do anything with it. + $Now = $Now + + $currentTime = [datetime]::UtcNow.ToLocalTime() + if ($PSCmdlet.ParameterSetName -eq "Expected") { + # do nothing we already have expected value + } + elseif ($PSCmdlet.ParameterSetName -eq "Now") { + $Expected = $currentTime + } + else { + if ($Ago -and $FromNow -or (-not $Ago -and -not $FromNow)) { + throw "You must provide either -Ago or -FromNow switch, but not both or none." + } + + if ($Ago) { + $Expected = $currentTime - (Get-TimeSpanFromStringWithUnit -Value $Time) + } + else { + $Expected = $currentTime + (Get-TimeSpanFromStringWithUnit -Value $Time) + } + } + + if ($Actual -le $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected the provided [datetime] to be after , but it was before: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/Time/Should-BeBefore.ps1 b/src/functions/assert/Time/Should-BeBefore.ps1 new file mode 100644 index 000000000..1cdb98bb7 --- /dev/null +++ b/src/functions/assert/Time/Should-BeBefore.ps1 @@ -0,0 +1,113 @@ +function Assert-Before { + <# + .SYNOPSIS + Asserts that the provided [datetime] is before the expected [datetime]. + + .PARAMETER Actual + The actual [datetime] value. + + .PARAMETER Expected + The expected [datetime] value. + + .PARAMETER Time + The time to add or subtract from the current time. This parameter uses fluent time syntax e.g. 1minute. + + .PARAMETER Ago + Indicates that the -Time should be subtracted from the current time. + + .PARAMETER FromNow + Indicates that the -Time should be added to the current time. + + .PARAMETER Now + Indicates that the current time should be used as the expected time. + + .PARAMETER Because + The reason why the actual value should be before the expected value. + + .EXAMPLE + ```powershell + (Get-Date).AddDays(-1) | Should-BeBefore (Get-Date) + ``` + + This assertion will pass, because the actual value is before the expected value. + + .EXAMPLE + ```powershell + (Get-Date).AddDays(1) | Should-BeBefore (Get-Date) + ``` + This assertion will fail, because the actual value is not before the expected value. + + .EXAMPLE + ```powershell + (Get-Date).AddMinutes(1) | Should-BeBefore 10minutes -FromNow + ``` + + This assertion will pass, because the actual value is before the expected value. + + .EXAMPLE + ```powershell + + (Get-Date).AddDays(-2) | Should-BeBefore -Time 3days -Ago + ``` + + This assertion will pass, because the actual value is before the expected value. + + .NOTES + The `Should-BeBefore` assertion is the opposite of the `Should-BeAfter` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeBefore + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + [CmdletBinding(DefaultParameterSetName = "Now")] + param ( + [Parameter(Position = 2, ValueFromPipeline = $true)] + $Actual, + + [Parameter(Position = 0, ParameterSetName = "Now")] + [switch] $Now, + + [Parameter(Position = 0, ParameterSetName = "Fluent")] + $Time, + + [Parameter(Position = 1, ParameterSetName = "Fluent")] + [switch] $Ago, + + [Parameter(Position = 1, ParameterSetName = "Fluent")] + [switch] $FromNow, + + [Parameter(Position = 0, ParameterSetName = "Expected")] + [DateTime] $Expected + ) + + # Now is just a syntax marker, we don't need to do anything with it. + $Now = $Now + + $currentTime = [datetime]::UtcNow.ToLocalTime() + if ($PSCmdlet.ParameterSetName -eq "Expected") { + # do nothing we already have expected value + } + elseif ($PSCmdlet.ParameterSetName -eq "Now") { + $Expected = $currentTime + } + else { + if ($Ago -and $FromNow -or (-not $Ago -and -not $FromNow)) { + throw "You must provide either -Ago or -FromNow switch, but not both or none." + } + + if ($Ago) { + $Expected = $currentTime - (Get-TimeSpanFromStringWithUnit -Value $Time) + } + else { + $Expected = $currentTime + (Get-TimeSpanFromStringWithUnit -Value $Time) + } + } + + if ($Actual -ge $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected the provided [datetime] to be before , but it was after: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } +} diff --git a/src/functions/assert/Time/Should-BeFasterThan.ps1 b/src/functions/assert/Time/Should-BeFasterThan.ps1 new file mode 100644 index 000000000..ee3fffb96 --- /dev/null +++ b/src/functions/assert/Time/Should-BeFasterThan.ps1 @@ -0,0 +1,72 @@ +function Assert-Faster { + <# + .SYNOPSIS + Asserts that the provided [timespan] or [scriptblock] is faster than the expected [timespan]. + + .PARAMETER Actual + The actual [timespan] or [scriptblock] value. + + .PARAMETER Expected + The expected [timespan] or fluent time value. + + .PARAMETER Because + The reason why the actual value should be faster than the expected value. + + .EXAMPLE + ```powershell + Measure-Command { Start-Sleep -Milliseconds 100 } | Should-BeFasterThan 1s + ``` + + This assertion will pass, because the actual value is faster than the expected value. + + .EXAMPLE + ```powershell + { Start-Sleep -Milliseconds 100 } | Should-BeFasterThan 50ms + ``` + + This assertion will fail, because the actual value is not faster than the expected value. + + .NOTES + The `Should-BeFasterThan` assertion is the opposite of the `Should-BeSlowerThan` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeFasterThan + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0)] + $Expected + ) + + if ($Expected -isnot [timespan]) { + $Expected = Get-TimeSpanFromStringWithUnit -Value $Expected + } + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -is [scriptblock]) { + $sw = [System.Diagnostics.Stopwatch]::StartNew() + & $Actual + $sw.Stop() + + if ($sw.Elapsed -ge $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $sw.Elapsed -Because $Because -Data @{ scriptblock = $Actual } -DefaultMessage "Expected the provided [scriptblock] to execute faster than , but it took to run.`nScriptBlock: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + return + } + + if ($Actual -is [timespan]) { + if ($Actual -ge $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "The provided [timespan] should be shorter than , but it was longer: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + return + } +} diff --git a/src/functions/assert/Time/Should-BeSlowerThan.ps1 b/src/functions/assert/Time/Should-BeSlowerThan.ps1 new file mode 100644 index 000000000..a20db90c2 --- /dev/null +++ b/src/functions/assert/Time/Should-BeSlowerThan.ps1 @@ -0,0 +1,80 @@ +function Assert-Slower { + <# + .SYNOPSIS + Asserts that the provided [timespan] is slower than the expected [timespan]. + + .PARAMETER Actual + The actual [timespan] or [scriptblock] value. + + .PARAMETER Expected + The expected [timespan] or fluent time value. + + .PARAMETER Because + The reason why the actual value should be slower than the expected value. + + .EXAMPLE + ```powershell + { Start-Sleep -Seconds 10 } | Should-BeSlowerThan 2seconds + ``` + + This assertion will pass, because the actual value is slower than the expected value. + + .EXAMPLE + ```powershell + [Timespan]::fromSeconds(10) | Should-BeSlowerThan 2seconds + ``` + + This assertion will pass, because the actual value is slower than the expected value. + + .EXAMPLE + ```powershell + + { Start-Sleep -Seconds 1 } | Should-BeSlowerThan 10seconds + ``` + + This assertion will fail, because the actual value is not slower than the expected value. + + .NOTES + The `Should-BeSlowerThan` assertion is the opposite of the `Should-BeFasterThan` assertion. + + .LINK + https://pester.dev/docs/commands/Should-BeSlowerThan + + .LINK + https://pester.dev/docs/assertions + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(Position = 1, ValueFromPipeline = $true)] + $Actual, + [Parameter(Position = 0)] + $Expected + ) + + if ($Expected -isnot [timespan]) { + $Expected = Get-TimeSpanFromStringWithUnit -Value $Expected + } + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -is [scriptblock]) { + $sw = [System.Diagnostics.Stopwatch]::StartNew() + & $Actual + $sw.Stop() + + if ($sw.Elapsed -le $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $sw.Elapsed -Because $Because -Data @{ scriptblock = $Actual } -DefaultMessage "The provided [scriptblock] should execute slower than , but it took to run.`nScriptBlock: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + return + } + + if ($Actual -is [timespan]) { + if ($Actual -le $Expected) { + $Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "The provided [timespan] should be longer than , but it was shorter: " + throw [Pester.Factory]::CreateShouldErrorRecord($Message, $MyInvocation.ScriptName, $MyInvocation.ScriptLineNumber, $MyInvocation.Line.TrimEnd([System.Environment]::NewLine), $true) + } + return + } +} diff --git a/src/functions/assertions/Be.ps1 b/src/functions/assertions/Be.ps1 index 0ac42406b..9afecfe34 100644 --- a/src/functions/assertions/Be.ps1 +++ b/src/functions/assertions/Be.ps1 @@ -1,5 +1,5 @@ #Be -function Should-Be ($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { +function Should-BeAssertion ($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Compares one object with another for equality @@ -69,16 +69,16 @@ function NotShouldBeFailureMessage($ActualValue, $ExpectedValue, $Because) { } & $script:SafeCommands['Add-ShouldOperator'] -Name Be ` - -InternalName Should-Be ` - -Test ${function:Should-Be} ` - -Alias 'EQ' ` + -InternalName Should-BeAssertion ` + -Test ${function:Should-BeAssertion} ` + -Alias 'EQ' ` -SupportsArrayInput Set-ShouldOperatorHelpMessage -OperatorName Be ` -HelpMessage 'Compares one object with another for equality and throws if the two objects are not the same.' #BeExactly -function Should-BeExactly($ActualValue, $ExpectedValue, $Because) { +function Should-BeAssertionExactly($ActualValue, $ExpectedValue, $Because) { <# .SYNOPSIS Compares one object with another for equality and throws if the @@ -147,8 +147,8 @@ function NotShouldBeExactlyFailureMessage($ActualValue, $ExpectedValue, $Because } & $script:SafeCommands['Add-ShouldOperator'] -Name BeExactly ` - -InternalName Should-BeExactly ` - -Test ${function:Should-BeExactly} ` + -InternalName Should-BeAssertionExactly ` + -Test ${function:Should-BeAssertionExactly} ` -Alias 'CEQ' ` -SupportsArrayInput diff --git a/src/functions/assertions/BeGreaterThan.ps1 b/src/functions/assertions/BeGreaterThan.ps1 index d3b1c004a..c72076e99 100644 --- a/src/functions/assertions/BeGreaterThan.ps1 +++ b/src/functions/assertions/BeGreaterThan.ps1 @@ -1,4 +1,4 @@ -function Should-BeGreaterThan($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { +function Should-BeGreaterThanAssertion($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Asserts that a number (or other comparable value) is greater than an expected value. @@ -48,7 +48,7 @@ function Should-BeLessOrEqual($ActualValue, $ExpectedValue, [switch] $Negate, [s This test also passes, as PowerShell evaluates `10 -le 10` as true. #> if ($Negate) { - return Should-BeGreaterThan -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$false -Because $Because + return Should-BeGreaterThanAssertion -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$false -Because $Because } if ($ActualValue -gt $ExpectedValue) { @@ -69,8 +69,8 @@ function Should-BeLessOrEqual($ActualValue, $ExpectedValue, [switch] $Negate, [s } & $script:SafeCommands['Add-ShouldOperator'] -Name BeGreaterThan ` - -InternalName Should-BeGreaterThan ` - -Test ${function:Should-BeGreaterThan} ` + -InternalName Should-BeGreaterThanAssertion ` + -Test ${function:Should-BeGreaterThanAssertion} ` -Alias 'GT' Set-ShouldOperatorHelpMessage -OperatorName BeGreaterThan ` diff --git a/src/functions/assertions/BeIn.ps1 b/src/functions/assertions/BeIn.ps1 index ed719d568..a029613bf 100644 --- a/src/functions/assertions/BeIn.ps1 +++ b/src/functions/assertions/BeIn.ps1 @@ -1,4 +1,4 @@ -function Should-BeIn($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { +function Should-BeInAssertion($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Asserts that a collection of values contain a specific value. @@ -45,8 +45,8 @@ } & $script:SafeCommands['Add-ShouldOperator'] -Name BeIn ` - -InternalName Should-BeIn ` - -Test ${function:Should-BeIn} + -InternalName Should-BeInAssertion ` + -Test ${function:Should-BeInAssertion} Set-ShouldOperatorHelpMessage -OperatorName BeIn ` -HelpMessage "Asserts that a collection of values contain a specific value. Uses PowerShell's -contains operator to confirm." diff --git a/src/functions/assertions/BeLessThan.ps1 b/src/functions/assertions/BeLessThan.ps1 index b983c79f9..50b3621a8 100644 --- a/src/functions/assertions/BeLessThan.ps1 +++ b/src/functions/assertions/BeLessThan.ps1 @@ -1,4 +1,4 @@ -function Should-BeLessThan($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { +function Should-BeLessThanAssertion($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Asserts that a number (or other comparable value) is lower than an expected value. @@ -48,7 +48,7 @@ function Should-BeGreaterOrEqual($ActualValue, $ExpectedValue, [switch] $Negate, This test also passes, as PowerShell evaluates `2 -ge 2` as true. #> if ($Negate) { - return Should-BeLessThan -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$false -Because $Because + return Should-BeLessThanAssertion -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$false -Because $Because } if ($ActualValue -lt $ExpectedValue) { @@ -69,8 +69,8 @@ function Should-BeGreaterOrEqual($ActualValue, $ExpectedValue, [switch] $Negate, } & $script:SafeCommands['Add-ShouldOperator'] -Name BeLessThan ` - -InternalName Should-BeLessThan ` - -Test ${function:Should-BeLessThan} ` + -InternalName Should-BeLessThanAssertion ` + -Test ${function:Should-BeLessThanAssertion} ` -Alias 'LT' Set-ShouldOperatorHelpMessage -OperatorName BeLessThan ` diff --git a/src/functions/assertions/BeLike.ps1 b/src/functions/assertions/BeLike.ps1 index 7a4320aff..5502ad56b 100644 --- a/src/functions/assertions/BeLike.ps1 +++ b/src/functions/assertions/BeLike.ps1 @@ -1,4 +1,4 @@ -function Should-BeLike($ActualValue, $ExpectedValue, [switch] $Negate, [String] $Because) { +function Should-BeLikeAssertion($ActualValue, $ExpectedValue, [switch] $Negate, [String] $Because) { <# .SYNOPSIS Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. @@ -53,8 +53,8 @@ } & $script:SafeCommands['Add-ShouldOperator'] -Name BeLike ` - -InternalName Should-BeLike ` - -Test ${function:Should-BeLike} + -InternalName Should-BeLikeAssertion ` + -Test ${function:Should-BeLikeAssertion} Set-ShouldOperatorHelpMessage -OperatorName BeLike ` -HelpMessage "Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. This comparison is not case-sensitive." diff --git a/src/functions/assertions/BeLikeExactly.ps1 b/src/functions/assertions/BeLikeExactly.ps1 index d476cc9fc..c0532a34d 100644 --- a/src/functions/assertions/BeLikeExactly.ps1 +++ b/src/functions/assertions/BeLikeExactly.ps1 @@ -1,4 +1,4 @@ -function Should-BeLikeExactly($ActualValue, $ExpectedValue, [switch] $Negate, [String] $Because) { +function Should-BeLikeExactlyAssertion($ActualValue, $ExpectedValue, [switch] $Negate, [String] $Because) { <# .SYNOPSIS Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. @@ -52,8 +52,8 @@ } & $script:SafeCommands['Add-ShouldOperator'] -Name BeLikeExactly ` - -InternalName Should-BeLikeExactly ` - -Test ${function:Should-BeLikeExactly} + -InternalName Should-BeLikeExactlyAssertion ` + -Test ${function:Should-BeLikeExactlyAssertion} Set-ShouldOperatorHelpMessage -OperatorName BeLikeExactly ` -HelpMessage "Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. This comparison is case-sensitive." diff --git a/src/functions/assertions/BeNullOrEmpty.ps1 b/src/functions/assertions/BeNullOrEmpty.ps1 index 99099c83c..92266b88b 100644 --- a/src/functions/assertions/BeNullOrEmpty.ps1 +++ b/src/functions/assertions/BeNullOrEmpty.ps1 @@ -1,5 +1,5 @@  -function Should-BeNullOrEmpty($ActualValue, [switch] $Negate, [string] $Because) { +function Should-BeNullOrEmptyAssertion($ActualValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Checks values for null or empty (strings). @@ -80,8 +80,8 @@ function NotShouldBeNullOrEmptyFailureMessage ($Because) { } & $script:SafeCommands['Add-ShouldOperator'] -Name BeNullOrEmpty ` - -InternalName Should-BeNullOrEmpty ` - -Test ${function:Should-BeNullOrEmpty} ` + -InternalName Should-BeNullOrEmptyAssertion ` + -Test ${function:Should-BeNullOrEmptyAssertion} ` -SupportsArrayInput Set-ShouldOperatorHelpMessage -OperatorName BeNullOrEmpty ` diff --git a/src/functions/assertions/BeOfType.ps1 b/src/functions/assertions/BeOfType.ps1 index 591dc1187..56cc9fd3b 100644 --- a/src/functions/assertions/BeOfType.ps1 +++ b/src/functions/assertions/BeOfType.ps1 @@ -1,5 +1,5 @@  -function Should-BeOfType($ActualValue, $ExpectedType, [switch] $Negate, [string]$Because) { +function Should-BeOfTypeAssertion($ActualValue, $ExpectedType, [switch] $Negate, [string]$Because) { <# .SYNOPSIS Asserts that the actual value should be an object of a specified type @@ -76,8 +76,8 @@ function Should-BeOfType($ActualValue, $ExpectedType, [switch] $Negate, [string] & $script:SafeCommands['Add-ShouldOperator'] -Name BeOfType ` - -InternalName Should-BeOfType ` - -Test ${function:Should-BeOfType} ` + -InternalName Should-BeOfTypeAssertion ` + -Test ${function:Should-BeOfTypeAssertion} ` -Alias 'HaveType' Set-ShouldOperatorHelpMessage -OperatorName BeOfType ` diff --git a/src/functions/assertions/BeTrueOrFalse.ps1 b/src/functions/assertions/BeTrueOrFalse.ps1 index 028363865..3c3c74297 100644 --- a/src/functions/assertions/BeTrueOrFalse.ps1 +++ b/src/functions/assertions/BeTrueOrFalse.ps1 @@ -1,4 +1,4 @@ -function Should-BeTrue($ActualValue, [switch] $Negate, [string] $Because) { +function Should-BeTrueAssertion($ActualValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Asserts that the value is true, or truthy. @@ -20,7 +20,7 @@ This test passes as a "truthy" result. #> if ($Negate) { - return Should-BeFalse -ActualValue $ActualValue -Negate:$false -Because $Because + return Should-BeFalseAssertion -ActualValue $ActualValue -Negate:$false -Because $Because } if (-not $ActualValue) { @@ -42,7 +42,7 @@ } } -function Should-BeFalse($ActualValue, [switch] $Negate, $Because) { +function Should-BeFalseAssertion($ActualValue, [switch] $Negate, $Because) { <# .SYNOPSIS Asserts that the value is false, or falsy. @@ -64,7 +64,7 @@ function Should-BeFalse($ActualValue, [switch] $Negate, $Because) { This test passes as a "falsy" result. #> if ($Negate) { - return Should-BeTrue -ActualValue $ActualValue -Negate:$false -Because $Because + return Should-BeTrueAssertion -ActualValue $ActualValue -Negate:$false -Because $Because } if ($ActualValue) { @@ -88,15 +88,15 @@ function Should-BeFalse($ActualValue, [switch] $Negate, $Because) { & $script:SafeCommands['Add-ShouldOperator'] -Name BeTrue ` - -InternalName Should-BeTrue ` - -Test ${function:Should-BeTrue} + -InternalName Should-BeTrueAssertion ` + -Test ${function:Should-BeTrueAssertion} Set-ShouldOperatorHelpMessage -OperatorName BeTrue ` -HelpMessage "Asserts that the value is true, or truthy." & $script:SafeCommands['Add-ShouldOperator'] -Name BeFalse ` - -InternalName Should-BeFalse ` - -Test ${function:Should-BeFalse} + -InternalName Should-BeFalseAssertion ` + -Test ${function:Should-BeFalseAssertion} Set-ShouldOperatorHelpMessage -OperatorName BeFalse ` -HelpMessage "Asserts that the value is false, or falsy." diff --git a/src/functions/assertions/Contain.ps1 b/src/functions/assertions/Contain.ps1 index 5da8d9a40..6ecd43f83 100644 --- a/src/functions/assertions/Contain.ps1 +++ b/src/functions/assertions/Contain.ps1 @@ -1,4 +1,4 @@ -function Should-Contain($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { +function Should-ContainAssertion($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Asserts that collection contains a specific value. @@ -45,8 +45,8 @@ } & $script:SafeCommands['Add-ShouldOperator'] -Name Contain ` - -InternalName Should-Contain ` - -Test ${function:Should-Contain} ` + -InternalName Should-ContainAssertion ` + -Test ${function:Should-ContainAssertion} ` -SupportsArrayInput Set-ShouldOperatorHelpMessage -OperatorName Contain ` diff --git a/src/functions/assertions/Exist.ps1 b/src/functions/assertions/Exist.ps1 index 2b5895b75..e6b0f58c8 100644 --- a/src/functions/assertions/Exist.ps1 +++ b/src/functions/assertions/Exist.ps1 @@ -1,4 +1,4 @@ -function Should-Exist($ActualValue, [switch] $Negate, [string] $Because) { +function Should-ExistAssertion($ActualValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Does not perform any comparison, but checks if the object calling Exist is present in a PS Provider. @@ -41,8 +41,8 @@ } & $script:SafeCommands['Add-ShouldOperator'] -Name Exist ` - -InternalName Should-Exist ` - -Test ${function:Should-Exist} + -InternalName Should-ExistAssertion ` + -Test ${function:Should-ExistAssertion} Set-ShouldOperatorHelpMessage -OperatorName Exist ` -HelpMessage "Does not perform any comparison, but checks if the object calling Exist is present in a PS Provider. The object must have valid path syntax. It essentially must pass a Test-Path call." diff --git a/src/functions/assertions/FileContentMatch.ps1 b/src/functions/assertions/FileContentMatch.ps1 index be0ab6b76..34daa431e 100644 --- a/src/functions/assertions/FileContentMatch.ps1 +++ b/src/functions/assertions/FileContentMatch.ps1 @@ -1,4 +1,4 @@ -function Should-FileContentMatch($ActualValue, $ExpectedContent, [switch] $Negate, $Because) { +function Should-FileContentMatchAssertion($ActualValue, $ExpectedContent, [switch] $Negate, $Because) { <# .SYNOPSIS Checks to see if a file contains the specified text. @@ -73,8 +73,8 @@ function NotShouldFileContentMatchFailureMessage($ActualValue, $ExpectedContent, } & $script:SafeCommands['Add-ShouldOperator'] -Name FileContentMatch ` - -InternalName Should-FileContentMatch ` - -Test ${function:Should-FileContentMatch} + -InternalName Should-FileContentMatchAssertion ` + -Test ${function:Should-FileContentMatchAssertion} Set-ShouldOperatorHelpMessage -OperatorName FileContentMatch ` -HelpMessage 'Checks to see if a file contains the specified text. This search is not case sensitive and uses regular expressions.' diff --git a/src/functions/assertions/FileContentMatchMultiline.ps1 b/src/functions/assertions/FileContentMatchMultiline.ps1 index 581d04491..61446887b 100644 --- a/src/functions/assertions/FileContentMatchMultiline.ps1 +++ b/src/functions/assertions/FileContentMatchMultiline.ps1 @@ -1,4 +1,4 @@ -function Should-FileContentMatchMultiline($ActualValue, $ExpectedContent, [switch] $Negate, [String] $Because) { +function Should-FileContentMatchMultilineAssertion($ActualValue, $ExpectedContent, [switch] $Negate, [String] $Because) { <# .SYNOPSIS As opposed to FileContentMatch and FileContentMatchExactly operators, @@ -65,8 +65,8 @@ function NotShouldFileContentMatchMultilineFailureMessage($ActualValue, $Expecte } & $script:SafeCommands['Add-ShouldOperator'] -Name FileContentMatchMultiline ` - -InternalName Should-FileContentMatchMultiline ` - -Test ${function:Should-FileContentMatchMultiline} + -InternalName Should-FileContentMatchMultilineAssertion ` + -Test ${function:Should-FileContentMatchMultilineAssertion} Set-ShouldOperatorHelpMessage -OperatorName FileContentMatchMultiline ` -HelpMessage "As opposed to FileContentMatch and FileContentMatchExactly operators, FileContentMatchMultiline presents content of the file being tested as one string object, so that the expression you are comparing it to can consist of several lines.`n`nWhen using FileContentMatchMultiline operator, '^' and '$' represent the beginning and end of the whole file, instead of the beginning and end of a line" diff --git a/src/functions/assertions/FileContentMatchMultilineExactly.ps1 b/src/functions/assertions/FileContentMatchMultilineExactly.ps1 index 472c17082..3e43764d7 100644 --- a/src/functions/assertions/FileContentMatchMultilineExactly.ps1 +++ b/src/functions/assertions/FileContentMatchMultilineExactly.ps1 @@ -1,4 +1,4 @@ -function Should-FileContentMatchMultilineExactly($ActualValue, $ExpectedContent, [switch] $Negate, [String] $Because) { +function Should-FileContentMatchMultilineExactlyAssertion($ActualValue, $ExpectedContent, [switch] $Negate, [String] $Because) { <# .SYNOPSIS As opposed to FileContentMatch and FileContentMatchExactly operators, @@ -82,8 +82,8 @@ function NotShouldFileContentMatchMultilineExactlyFailureMessage($ActualValue, $ } & $script:SafeCommands['Add-ShouldOperator'] -Name FileContentMatchMultilineExactly ` - -InternalName Should-FileContentMatchMultilineExactly ` - -Test ${function:Should-FileContentMatchMultilineExactly} + -InternalName Should-FileContentMatchMultilineExactlyAssertion ` + -Test ${function:Should-FileContentMatchMultilineExactlyAssertion} Set-ShouldOperatorHelpMessage -OperatorName FileContentMatchMultilineExactly ` -HelpMessage "As opposed to FileContentMatch and FileContentMatchExactly operators, FileContentMatchMultilineExactly presents content of the file being tested as one string object, so that the case sensitive expression you are comparing it to can consist of several lines.`n`nWhen using FileContentMatchMultilineExactly operator, '^' and '$' represent the beginning and end of the whole file, instead of the beginning and end of a line." diff --git a/src/functions/assertions/HaveCount.ps1 b/src/functions/assertions/HaveCount.ps1 index c3857818d..a4bf45574 100644 --- a/src/functions/assertions/HaveCount.ps1 +++ b/src/functions/assertions/HaveCount.ps1 @@ -1,4 +1,4 @@ -function Should-HaveCount($ActualValue, [int] $ExpectedValue, [switch] $Negate, [string] $Because) { +function Should-HaveCountAssertion($ActualValue, [int] $ExpectedValue, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Asserts that a collection has the expected amount of items. @@ -87,8 +87,8 @@ } & $script:SafeCommands['Add-ShouldOperator'] -Name HaveCount ` - -InternalName Should-HaveCount ` - -Test ${function:Should-HaveCount} ` + -InternalName Should-HaveCountAssertion ` + -Test ${function:Should-HaveCountAssertion} ` -SupportsArrayInput Set-ShouldOperatorHelpMessage -OperatorName HaveCount ` diff --git a/src/functions/assertions/HaveParameter.ps1 b/src/functions/assertions/HaveParameter.ps1 index dd5d35f25..684cda26b 100644 --- a/src/functions/assertions/HaveParameter.ps1 +++ b/src/functions/assertions/HaveParameter.ps1 @@ -1,4 +1,4 @@ -function Should-HaveParameter ( +function Should-HaveParameterAssertion ( $ActualValue, [String] $ParameterName, $Type, @@ -383,8 +383,8 @@ } & $script:SafeCommands['Add-ShouldOperator'] -Name HaveParameter ` - -InternalName Should-HaveParameter ` - -Test ${function:Should-HaveParameter} + -InternalName Should-HaveParameterAssertion ` + -Test ${function:Should-HaveParameterAssertion} Set-ShouldOperatorHelpMessage -OperatorName HaveParameter ` -HelpMessage 'Asserts that a command has the expected parameter.' diff --git a/src/functions/assertions/Match.ps1 b/src/functions/assertions/Match.ps1 index ef80fa581..1439c4cb3 100644 --- a/src/functions/assertions/Match.ps1 +++ b/src/functions/assertions/Match.ps1 @@ -1,4 +1,4 @@ -function Should-Match($ActualValue, $RegularExpression, [switch] $Negate, [string] $Because) { +function Should-MatchAssertion($ActualValue, $RegularExpression, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Uses a regular expression to compare two objects. @@ -62,8 +62,8 @@ function NotShouldMatchFailureMessage($ActualValue, $RegularExpression, $Because } & $script:SafeCommands['Add-ShouldOperator'] -Name Match ` - -InternalName Should-Match ` - -Test ${function:Should-Match} + -InternalName Should-MatchAssertion ` + -Test ${function:Should-MatchAssertion} Set-ShouldOperatorHelpMessage -OperatorName Match ` -HelpMessage 'Uses a regular expression to compare two objects. This comparison is not case sensitive.' diff --git a/src/functions/assertions/MatchExactly.ps1 b/src/functions/assertions/MatchExactly.ps1 index e2c65da80..aac2a0888 100644 --- a/src/functions/assertions/MatchExactly.ps1 +++ b/src/functions/assertions/MatchExactly.ps1 @@ -1,4 +1,4 @@ -function Should-MatchExactly($ActualValue, $RegularExpression, [switch] $Negate, [string] $Because) { +function Should-MatchExactlyAssertion($ActualValue, $RegularExpression, [switch] $Negate, [string] $Because) { <# .SYNOPSIS Uses a regular expression to compare two objects. @@ -55,8 +55,8 @@ function NotShouldMatchExactlyFailureMessage($ActualValue, $RegularExpression) { } & $script:SafeCommands['Add-ShouldOperator'] -Name MatchExactly ` - -InternalName Should-MatchExactly ` - -Test ${function:Should-MatchExactly} ` + -InternalName Should-MatchExactlyAssertion ` + -Test ${function:Should-MatchExactlyAssertion} ` -Alias 'CMATCH' Set-ShouldOperatorHelpMessage -OperatorName MatchExactly ` diff --git a/src/functions/assertions/PesterThrow.ps1 b/src/functions/assertions/PesterThrow.ps1 index f9bd97946..311b086e0 100644 --- a/src/functions/assertions/PesterThrow.ps1 +++ b/src/functions/assertions/PesterThrow.ps1 @@ -1,4 +1,4 @@ -function Should-Throw { +function Should-ThrowAssertion { <# .SYNOPSIS Checks if an exception was thrown. Enclose input in a script block. @@ -168,8 +168,8 @@ function NotShouldThrowFailureMessage { } & $script:SafeCommands['Add-ShouldOperator'] -Name Throw ` - -InternalName Should-Throw ` - -Test ${function:Should-Throw} + -InternalName Should-ThrowAssertion ` + -Test ${function:Should-ThrowAssertion} Set-ShouldOperatorHelpMessage -OperatorName Throw ` -HelpMessage 'Checks if an exception was thrown. Enclose input in a scriptblock.' diff --git a/src/functions/assertions/Should.ps1 b/src/functions/assertions/Should.ps1 index 549ff7222..c130e6a64 100644 --- a/src/functions/assertions/Should.ps1 +++ b/src/functions/assertions/Should.ps1 @@ -9,16 +9,6 @@ return (& $failureMessageFunction $value $expected) } -function New-ShouldErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText, $Terminating) { - $exception = [Exception] $Message - $errorID = 'PesterAssertionFailed' - $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult - # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system. - $targetObject = @{ Message = $Message; File = $File; Line = $Line; LineText = $LineText; Terminating = $Terminating } - $errorRecord = [Management.Automation.ErrorRecord]::new($exception, $errorID, $errorCategory, $targetObject) - return $errorRecord -} - function Should { <# .SYNOPSIS diff --git a/test.ps1 b/test.ps1 index 6a905b6ab..b5b4edbe3 100644 --- a/test.ps1 +++ b/test.ps1 @@ -116,6 +116,17 @@ New-Module -Name TestHelpers -ScriptBlock { $module = Get-Module -Name Pester -ErrorAction Stop . $module $ScriptBlock } + + function New-Dictionary ([hashtable]$Hashtable) { + $d = [System.Collections.Generic.Dictionary[string, object]]::new() + $Hashtable.GetEnumerator() | ForEach-Object { $d.Add($_.Key, $_.Value) } + + $d + } + + function Clear-WhiteSpace ($Text) { + "$($Text -replace "(`t|`n|`r)"," " -replace "\s+"," ")".Trim() + } } | Out-Null diff --git a/tst/Format2.Tests.ps1 b/tst/Format2.Tests.ps1 new file mode 100644 index 000000000..e6c935bbc --- /dev/null +++ b/tst/Format2.Tests.ps1 @@ -0,0 +1,219 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + + BeforeDiscovery { + Add-Type -TypeDefinition ' + namespace Assertions.TestType { + public class Person { + // powershell v2 mandates fully implemented properties + string _name; + int _age; + public string Name { get { return _name; } set { _name = value; } } + public int Age { get { return _age; } set { _age = value; } } + } + }' + } + + + Describe "Format-Collection2" { + It "Formats empty collection to @()" -TestCases @( + ) { + Format-Collection2 -Value @() | Verify-Equal "@()" + } + + It "Formats collection of values '' to '' using the default separator" -TestCases @( + @{ Value = (1, 2, 3); Expected = "@(1, 2, 3)" } + ) { + Format-Collection2 -Value $Value | Verify-Equal $Expected + } + + It "Formats collection of values '' to '' using the default separator" -TestCases @( + @{ Value = (1, 2, 3); Expected = "@(1, 2, 3)" } + ) { + Format-Collection2 -Value $Value | Verify-Equal $Expected + } + + It "Formats collection on single line when it is shorter than 50 characters" -TestCases @( + @{ Value = (1, 2, 3); Expected = "@(1, 2, 3)" } + @{ Value = @([string]::new("*", 44)); Expected = "@('$([string]::new("*", 44))')" } + ) { + Format-Collection2 -Value $Value -Pretty | Verify-Equal $Expected + } + + It "Formats collection on multiple lines when it is longer than 50 characters" -TestCases @( + @{ Value = ([string]::new("*", 25), [string]::new("-", 25)); Expected = "@(`n '$([string]::new("*", 25))',`n '$([string]::new("-", 25))'`n)" } + @{ Value = @([string]::new("*", 60)); Expected = "@(`n '$([string]::new("*", 60))'`n)" } + ) { + Format-Collection2 -Value $Value -Pretty | Verify-Equal $Expected + } + } + + Describe "Format-Number" { + It "Formats number to use . separator (tests anything only on non-english systems --todo)" -TestCases @( + @{ Value = 1.1; }, + @{ Value = [double] 1.1; }, + @{ Value = [float] 1.1; }, + @{ Value = [single] 1.1; }, + @{ Value = [decimal] 1.1; } + ) { + param ($Value) + Format-Number -Value $Value | Verify-Equal "1.1" + } + } + + Describe "Format-Object2" { + It "Formats object '' to ''" -TestCases @( + @{ Value = ([PSCustomObject]@{Name = 'Jakub'; Age = 28 }); Expected = "PSObject{Age=28; Name='Jakub'}" }, + @{ Value = (New-Object -Type Assertions.TestType.Person -Property @{Name = 'Jakub'; Age = 28 }); Expected = "Assertions.TestType.Person{Age=28; Name='Jakub'}" } + ) { + param ($Value, $Expected) + Format-Object2 -Value $Value | Verify-Equal $Expected + } + + It "Formats object '' with selected properties '' to ''" -TestCases @( + @{ Value = ([PSCustomObject]@{Name = 'Jakub'; Age = 28 }); SelectedProperties = "Age"; Expected = "PSObject{Age=28}" }, + @{ + Value = (New-Object -Type Assertions.TestType.Person -Property @{Name = 'Jakub'; Age = 28 }) + SelectedProperties = 'Name' + Expected = "Assertions.TestType.Person{Name='Jakub'}" + } + ) { + param ($Value, $SelectedProperties, $Expected) + Format-Object2 -Value $Value -Property $SelectedProperties | Verify-Equal $Expected + } + + It "Formats current process with selected properties Name and Id correctly" { + # this used to be a normal unit test but Idle process does not exist + # cross platform so we use the current process, which can also have + # different names among powershell versions + $process = Get-Process -PID $PID + $name = $process.Name + $id = $process.Id + $SelectedProperties = "Name", "Id" + $expected = "Diagnostics.Process{Id=$id; Name='$name'}" + + Format-Object2 -Value $process -Property $selectedProperties | Verify-Equal $Expected + } + } + + Describe "Format-Boolean2" { + It "Formats boolean '' to ''" -TestCases @( + @{ Value = $true; Expected = '$true' }, + @{ Value = $false; Expected = '$false' } + ) { + param($Value, $Expected) + Format-Boolean2 -Value $Value | Verify-Equal $Expected + } + } + + Describe "Format-Null2" { + It "Formats null to '`$null'" { + Format-Null2 | Verify-Equal '$null' + } + } + + Describe "Format-ScriptBlock2" { + It "Formats scriptblock as string with curly braces" { + Format-ScriptBlock2 -Value { abc } | Verify-Equal '{ abc }' + } + } + + Describe "Format-Hashtable2" { + It "Formats empty hashtable as @{}" { + Format-Hashtable2 @{} | Verify-Equal '@{}' + } + + It "Formats hashtable as ''" -TestCases @( + @{ Value = @{Age = 28; Name = 'Jakub' }; Expected = "@{Age=28; Name='Jakub'}" } + @{ Value = @{Z = 1; H = 1; A = 1 }; Expected = '@{A=1; H=1; Z=1}' } + @{ Value = @{Hash = @{Hash = 'Value' } }; Expected = "@{Hash=@{Hash='Value'}}" } + ) { + param ($Value, $Expected) + Format-Hashtable2 $Value | Verify-Equal $Expected + } + } + + Describe "Format-Dictionary2" { + It "Formats empty dictionary as @{}" { + Format-Dictionary2 (New-Dictionary @{}) | Verify-Equal 'Dictionary{}' + } + + It "Formats dictionary as ''" -TestCases @( + @{ Value = New-Dictionary @{Age = 28; Name = 'Jakub' }; Expected = "Dictionary{Age=28; Name='Jakub'}" } + @{ Value = New-Dictionary @{Z = 1; H = 1; A = 1 }; Expected = 'Dictionary{A=1; H=1; Z=1}' } + @{ Value = New-Dictionary @{Dict = ( New-Dictionary @{Dict = 'Value' }) }; Expected = "Dictionary{Dict=Dictionary{Dict='Value'}}" } + ) { + param ($Value, $Expected) + Format-Dictionary2 $Value | Verify-Equal $Expected + } + } + + Describe "Format-Nicely2" { + It "Formats value '' correctly to ''" -TestCases @( + @{ Value = $null; Expected = '$null' } + @{ Value = $true; Expected = '$true' } + @{ Value = $false; Expected = '$false' } + @{ Value = 'a' ; Expected = "'a'" }, + @{ Value = 1; Expected = '1' }, + @{ Value = (1, 2, 3); Expected = '@(1, 2, 3)' }, + @{ Value = 1.1; Expected = '1.1' }, + @{ Value = [int]; Expected = '[int]' } + @{ Value = [PSCustomObject]@{ Name = "Jakub" }; Expected = "PSObject{Name='Jakub'}" }, + @{ Value = (New-Object -Type Assertions.TestType.Person -Property @{Name = 'Jakub'; Age = 28 }); Expected = "Assertions.TestType.Person{Age=28; Name='Jakub'}" } + @{ Value = @{Name = 'Jakub'; Age = 28 }; Expected = "@{Age=28; Name='Jakub'}" } + @{ Value = New-Dictionary @{Age = 28; Name = 'Jakub' }; Expected = "Dictionary{Age=28; Name='Jakub'}" } + ) { + Format-Nicely2 -Value $Value | Verify-Equal $Expected + } + } + + Describe "Get-DisplayProperty2" { + It "Returns '' for ''" -TestCases @( + @{ Type = "Diagnostics.Process"; Expected = ("Id", "Name") } + ) { + param ($Type, $Expected) + $Actual = Get-DisplayProperty2 -Type $Type + "$Actual" | Verify-Equal "$Expected" + } + } + + Describe "Format-Type2" { + It "Given '' it returns the correct shortened type name ''" -TestCases @( + @{ Value = [int]; Expected = '[int]' }, + @{ Value = [double]; Expected = '[double]' }, + @{ Value = [string]; Expected = '[string]' }, + @{ Value = $null; Expected = '[null]' }, + @{ Value = [Management.Automation.PSObject]; Expected = '[PSObject]' }, + @{ Value = [Object[]]; Expected = '[collection]' } + ) { + param($Value, $Expected) + Format-Type2 -Value $Value | Verify-Equal $Expected + } + } + + + Describe "Get-ShortType2" { + It "Given '' it returns the correct shortened type name ''" -TestCases @( + @{ Value = 1; Expected = '[int]' }, + @{ Value = 1.1; Expected = '[double]' }, + @{ Value = 'a' ; Expected = '[string]' }, + @{ Value = $null ; Expected = '[null]' }, + @{ Value = [PSCustomObject]@{Name = 'Jakub' } ; Expected = '[PSObject]' }, + @{ Value = [Object[]]1, 2, 3 ; Expected = '[collection]' } + ) { + param($Value, $Expected) + Get-ShortType2 -Value $Value | Verify-Equal $Expected + } + } + + Describe "Format-String2" { + It "Formats empty string to `` (no quotes)" { + Format-String2 -Value "" | Verify-Equal '' + } + + It "Formats string to be sorrounded by quotes" { + Format-String2 -Value "abc" | Verify-Equal "'abc'" + } + } +} diff --git a/tst/Help.Tests.ps1 b/tst/Help.Tests.ps1 index 1864d37a5..c9ff59c1d 100644 --- a/tst/Help.Tests.ps1 +++ b/tst/Help.Tests.ps1 @@ -32,8 +32,12 @@ Describe "Testing module help" -Tag 'Help' -ForEach @{ exportedFunctions = $expo It 'Has link sections' { $help.psobject.properties.name -match 'relatedLinks' | Should -Not -BeNullOrEmpty -Because 'all exported functions should at least have link to online version as first Uri' + $functionName = $_.Name + $alias = Get-Alias -Name Should* | Where-Object { $_.Definition -eq $functionName } + $helpName = if ($alias) { $alias.Name } else { $help.Name } + $firstUri = $help.relatedLinks.navigationLink | Where-Object uri | Select-Object -First 1 -ExpandProperty uri - $firstUri | Should -Be "https://pester.dev/docs/commands/$($help.Name)" -Because 'first uri-link should be to online version of this help topic' + $firstUri | Should -Be "https://pester.dev/docs/commands/$helpName" -Because 'first uri-link should be to online version of this help topic' } # Skipping Assert-MockCalled and Assert-VerifiableMock which are deprecated and missing docs @@ -68,7 +72,7 @@ Describe "Testing module help" -Tag 'Help' -ForEach @{ exportedFunctions = $expo (Get-AssertionDynamicParams).Values | Where-Object name -in $operators } - $parametersMissingHelp = @($operatorParams |Where-Object { + $parametersMissingHelp = @($operatorParams | Where-Object { $attr = $_.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } $null -eq $attr -or $attr.HelpMessage -eq $null } | ForEach-Object Name) diff --git a/tst/axiom/Axiom.psm1 b/tst/axiom/Axiom.psm1 index 1d6c03703..7afe23178 100644 --- a/tst/axiom/Axiom.psm1 +++ b/tst/axiom/Axiom.psm1 @@ -13,4 +13,6 @@ . $PSScriptRoot\Verify-Type.ps1 +. $PSScriptRoot\Verify-Like.ps1 + . $PSScriptRoot\Verify-AssertionFailed.ps1 diff --git a/tst/axiom/Verify-Like.ps1 b/tst/axiom/Verify-Like.ps1 new file mode 100644 index 000000000..ef014230c --- /dev/null +++ b/tst/axiom/Verify-Like.ps1 @@ -0,0 +1,19 @@ + +function Verify-Like { + param ( + [Parameter(ValueFromPipeline = $true)] + $Actual, + [Parameter(Mandatory = $true, Position = 0)] + $Expected + ) + + if ($Actual -notlike $Expected) { + $message = "Expected is not present in Actual!`n" + + "Expected: '$Expected'`n" + + "Actual : '$Actual'" + + throw [Exception]$message + } + + $Actual +} diff --git a/tst/functions/assert/Boolean/Should-BeFalse.Tests.ps1 b/tst/functions/assert/Boolean/Should-BeFalse.Tests.ps1 new file mode 100644 index 000000000..cb0f5b1c8 --- /dev/null +++ b/tst/functions/assert/Boolean/Should-BeFalse.Tests.ps1 @@ -0,0 +1,39 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeFalse" { + It "Passes when given `$false" { + $false | Should-BeFalse + } + + It "Falis when given falsy value ''" -TestCases @( + @{ Actual = 0 } + @{ Actual = "" } + @{ Actual = $null } + @{ Actual = @() } + ) { + { Should-BeFalse -Actual $Actual } | Verify-AssertionFailed + } + + It "Fails for array input even if the last item is `$false" { + { $true, $true, $false | Should-BeFalse } | Verify-AssertionFailed + } + + Context "Validate messages" { + It "Given value '' that is not `$false it returns expected message ''" -TestCases @( + @{ Actual = $true ; Message = "Expected [bool] `$false, but got: [bool] `$true." }, + @{ Actual = 10 ; Message = "Expected [bool] `$false, but got: [int] 10." } + ) { + $err = { Should-BeFalse -Actual $Actual } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = $false + $expected | Should-BeFalse | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeFalse $true } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/Boolean/Should-BeFalsy.Tests.ps1 b/tst/functions/assert/Boolean/Should-BeFalsy.Tests.ps1 new file mode 100644 index 000000000..01f30533b --- /dev/null +++ b/tst/functions/assert/Boolean/Should-BeFalsy.Tests.ps1 @@ -0,0 +1,40 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeFalsy" { + It "Passes when given `$false" { + $false | Should-BeFalsy + } + + It "Passes when given falsy value ''" -TestCases @( + @{ Actual = 0 } + @{ Actual = "" } + @{ Actual = $null } + @{ Actual = @() } + ) { + param($Actual) + Should-BeFalsy -Actual $Actual + } + + It "Fails for array input even if the last item is `$false" { + { $true, $true, $false | Should-BeFalsy } | Verify-AssertionFailed + } + + Context "Validate messages" { + It "Given value '' that is not `$false it returns expected message ''" -TestCases @( + @{ Actual = $true ; Message = "Expected [bool] `$false or a falsy value: 0, """", `$null or @(), but got: [bool] `$true." }, + @{ Actual = 10 ; Message = "Expected [bool] `$false or a falsy value: 0, """", `$null or @(), but got: [int] 10." } + ) { + $err = { Should-BeFalsy -Actual $Actual } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = $false + $expected | Should-BeFalsy | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeFalsy $true } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/Boolean/Should-BeTrue.Tests.ps1 b/tst/functions/assert/Boolean/Should-BeTrue.Tests.ps1 new file mode 100644 index 000000000..3852bb318 --- /dev/null +++ b/tst/functions/assert/Boolean/Should-BeTrue.Tests.ps1 @@ -0,0 +1,36 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeTrue" { + It "Passes when given `$true" { + $true | Should-BeTrue + } + + It "Fails when given truthy value" -TestCases @( + @{ Actual = 1 } + @{ Actual = "text" } + @{ Actual = New-Object -TypeName PSObject } + @{ Actual = 1, 2 } + @{ Actual = "false" } + ) { + { Should-BeTrue -Actual $Actual } | Verify-AssertionFailed + } + + Context "Validate messages" { + It "Given value that is not `$true it returns expected message ''" -TestCases @( + @{ Actual = $false ; Message = "Expected [bool] `$true, but got: [bool] `$false." }, + @{ Actual = 0 ; Message = "Expected [bool] `$true, but got: [int] 0." } + ) { + $err = { Should-BeTrue -Actual $Actual } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = $true + $expected | Should-BeTrue | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeTrue $false } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/Boolean/Should-BeTruthy.Tests.ps1 b/tst/functions/assert/Boolean/Should-BeTruthy.Tests.ps1 new file mode 100644 index 000000000..446cf4fe2 --- /dev/null +++ b/tst/functions/assert/Boolean/Should-BeTruthy.Tests.ps1 @@ -0,0 +1,35 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeTruthy" { + It "Passes when given `$true" { + $true | Should-BeTruthy + } + + It "Passes when given truthy" -TestCases @( + @{ Actual = 1 } + @{ Actual = "text" } + @{ Actual = New-Object -TypeName PSObject } + @{ Actual = 1, 2 } + ) { + Should-BeTruthy -Actual $Actual + } + + Context "Validate messages" { + It "Given value that is not `$true it returns expected message ''" -TestCases @( + @{ Actual = $false ; Message = "Expected [bool] `$true or a truthy value, but got: [bool] `$false." }, + @{ Actual = 0 ; Message = "Expected [bool] `$true or a truthy value, but got: [int] 0." } + ) { + $err = { Should-BeTruthy -Actual $Actual } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = $true + $expected | Should-BeTruthy | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeTruthy $false } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/Collection/Should-All.Tests.ps1 b/tst/functions/assert/Collection/Should-All.Tests.ps1 new file mode 100644 index 000000000..49cd80b7a --- /dev/null +++ b/tst/functions/assert/Collection/Should-All.Tests.ps1 @@ -0,0 +1,67 @@ +Set-StrictMode -Version Latest + +Describe "Should-All" { + It "Passes when all items in the given collection pass the predicate" -TestCases @( + @{ Actual = 1, 1, 1, 1 } + @{ Actual = @(1) } + @{ Actual = 1 } + ) { + $Actual | Should-All -FilterScript { $_ -eq 1 } + } + + It "Fails when any item in the given collection does not pass the predicate" -TestCases @( + @{ Actual = 1, 1, 2, 1 } + @{ Actual = @(2) } + @{ Actual = 2 } + ) { + { $Actual | Should-All -FilterScript { $_ -eq 1 } } | Verify-AssertionFailed + } + + It "Can be failed by other assertion" { + $err = { 1, 1, 1 | Should-All -FilterScript { $_ | Should-Be 2 } } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal ("Expected all items in collection @(1, 1, 1) to pass filter { `$_ | Should-Be 2 }, but 3 of them @(1, 1, 1) did not pass the filter. +Reasons : +Expected [int] 2, but got [int] 1. +Expected [int] 2, but got [int] 1. +Expected [int] 2, but got [int] 1." -replace "`r`n", "`n") + } + + It "Fails when no items are passed" -TestCases @( + @{ Actual = $null; Expected = "Expected all items in collection @(`$null) to pass filter { `$_ -eq 1 }, but 1 of them `$null did not pass the filter." } + @{ Actual = @(); Expected = "Expected all items in collection to pass filter { `$_ -eq 1 }, but [collection] @() contains no items to compare." } + ) { + $err = { $Actual | Should-All -FilterScript { $_ -eq 1 } } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Expected + } + + It "Fails when no items are passed" { + { Should-All -FilterScript { $_ -eq 1 } } | Verify-AssertionFailed + } + + It "Validate messages" -TestCases @( + @{ Actual = @(3, 4, 5); Message = "Expected all items in collection @(3, 4, 5) to pass filter { `$_ -eq 1 }, but 3 of them @(3, 4, 5) did not pass the filter." } + ) { + $err = { $Actual | Should-All -FilterScript { $_ -eq 1 } } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + + It "Returns the value on output" { + $expected = "a", "b" + $v = $expected | Should-All { $true } + $v[0] | Verify-Equal $expected[0] + $v[1] | Verify-Equal $expected[1] + } + + It "Can filter using variables from the sorrounding context" { + $f = 1 + 2, 4 | Should-All { $_ / $f } + } + + It "Accepts FilterScript and Actual by position" { + Should-All { $true } 1, 2 + } + + It 'It fails when the only item not matching the filter is 0' { + { 0 | Should-All -FilterScript { $_ -gt 0 } } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/Collection/Should-Any.Tests.ps1 b/tst/functions/assert/Collection/Should-Any.Tests.ps1 new file mode 100644 index 000000000..453f9a9be --- /dev/null +++ b/tst/functions/assert/Collection/Should-Any.Tests.ps1 @@ -0,0 +1,65 @@ +Set-StrictMode -Version Latest + +Describe "Should-Any" { + It "Passes when at least one item in the given collection passes the predicate" -TestCases @( + @{ Actual = @(1, 2, 3) } + @{ Actual = @(1) } + @{ Actual = 1 } + ) { + $Actual | Should-Any -FilterScript { $_ -eq 1 } + } + + It "Fails when none of the items passes the predicate" -TestCases @( + @{ Actual = @(1, 2, 3) } + @{ Actual = @(1) } + @{ Actual = 1 } + ) { + { $Actual | Should-Any -FilterScript { $_ -eq 0 } } | Verify-AssertionFailed + } + + It "Can be failed by other assertion" { + $err = { 1, 1, 1 | Should-Any -FilterScript { $_ | Should-Be 2 } } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal ("Expected at least one item in collection @(1, 1, 1) to pass filter { `$_ | Should-Be 2 }, but none of the items passed the filter. +Reasons : +Expected [int] 2, but got [int] 1. +Expected [int] 2, but got [int] 1. +Expected [int] 2, but got [int] 1." -replace "`r`n", "`n") + } + + It "Fails when no items are passed" -TestCases @( + @{ Actual = $null; Expected = 'Expected at least one item in collection @($null) to pass filter { $_ -eq 1 }, but none of the items passed the filter.' } + @{ Actual = @(); Expected = 'Expected at least one item in collection to pass filter { $_ -eq 1 }, but [collection] @() contains no items to compare.' } + ) { + $err = { $Actual | Should-Any -FilterScript { $_ -eq 1 } } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Expected + } + + It "Fails when no items are passed" { + { Should-Any -FilterScript { $_ -eq 1 } } | Verify-AssertionFailed + } + + It "Can filter using variables from the sorrounding context" { + $f = 1 + 2, 4 | Should-Any { $_ / $f } + } + + It "Validate messages" -TestCases @( + @{ Actual = @(3, 4, 5); Message = "Expected at least one item in collection @(3, 4, 5) to pass filter { `$_ -eq 1 }, but none of the items passed the filter." } + @{ Actual = 3; Message = "Expected at least one item in collection 3 to pass filter { `$_ -eq 1 }, but none of the items passed the filter." } + @{ Actual = 3; Message = "Expected at least one item in collection 3 to pass filter { `$_ -eq 1 }, but none of the items passed the filter." } + ) { + $err = { $Actual | Should-Any -FilterScript { $_ -eq 1 } } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + + It "Returns the value on output" { + $expected = "a", "b" + $v = $expected | Should-Any { $true } + $v[0] | Verify-Equal $expected[0] + $v[1] | Verify-Equal $expected[1] + } + + It "Accepts FilterScript and Actual by position" { + Should-Any { $true } 1, 2 + } +} diff --git a/tst/functions/assert/Collection/Should-BeCollection.Tests.ps1 b/tst/functions/assert/Collection/Should-BeCollection.Tests.ps1 new file mode 100644 index 000000000..3b9d6139c --- /dev/null +++ b/tst/functions/assert/Collection/Should-BeCollection.Tests.ps1 @@ -0,0 +1,36 @@ +Set-StrictMode -Version Latest + +# TODO: Implement the Should-BeCollection tests, I just don't want to remove it from the current PR just to put it back afterwards. +return + +InPesterModuleScope { + Describe "Should-BeCollection" { + It "Passes when collections have the same count and items" -ForEach @( + @{ Actual = @(1); Expected = @(1) } + @{ Actual = @(1, 2); Expected = @(1, 2) } + ) { + $actual | Should-BeCollection $expected + } + + It "Fails when collections don't have the same count" -ForEach @( + @{ Actual = @(1); Expected = @(1, 2) } + @{ Actual = @(1, 2); Expected = @(1) } + ) { + $err = { $actual | Should-BeCollection $expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected int '1' to be present in collection '5', but it was not there." + } + + # It "Passes when collection of multiple items contains the expected item" { + # @(1,2,3) | Assert-Contain 1 + # } + + # It "Fails when collection of multiple items does not contain the expected item" { + # $err = { @(5,6,7) | Assert-Contain 1 } | Verify-AssertionFailed + # $err.Exception.Message | Verify-Equal "Expected int '1' to be present in collection '5, 6, 7', but it was not there." + # } + + # It "Can be called with positional parameters" { + # { Assert-Contain 1 3,4,5 } | Verify-AssertionFailed + # } + } +} diff --git a/tst/functions/assert/Collection/Should-ContainCollection.Tests.ps1 b/tst/functions/assert/Collection/Should-ContainCollection.Tests.ps1 new file mode 100644 index 000000000..6061a868b --- /dev/null +++ b/tst/functions/assert/Collection/Should-ContainCollection.Tests.ps1 @@ -0,0 +1,27 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Should-ContainCollection" { + It "Passes when collection of single item contains the expected item" { + @(1) | Should-ContainCollection 1 + } + + It "Fails when collection of single item does not contain the expected item" { + $err = { @(5) | Should-ContainCollection 1 } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected [int] 1 to be present in collection 5, but it was not there." + } + + It "Passes when collection of multiple items contains the expected item" { + @(1, 2, 3) | Should-ContainCollection 1 + } + + It "Fails when collection of multiple items does not contain the expected item" { + $err = { @(5, 6, 7) | Should-ContainCollection 1 } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected [int] 1 to be present in collection @(5, 6, 7), but it was not there." + } + + It "Can be called with positional parameters" { + { Should-ContainCollection 1 3, 4, 5 } | Verify-AssertionFailed + } + } +} diff --git a/tst/functions/assert/Collection/Should-NotContainCollection.Tests.ps1 b/tst/functions/assert/Collection/Should-NotContainCollection.Tests.ps1 new file mode 100644 index 000000000..1ab0f9255 --- /dev/null +++ b/tst/functions/assert/Collection/Should-NotContainCollection.Tests.ps1 @@ -0,0 +1,27 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Should-NotContainCollection" { + It "Fails when collection of single item contains the expected item" { + $err = { @(1) | Should-NotContainCollection 1 } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected [int] 1 to not be present in collection 1, but it was there." + } + + It "Passes when collection of single item does not contain the expected item" { + @(5) | Should-NotContainCollection 1 + } + + It "Fails when collection of multiple items contains the expected item" { + $err = { @(1, 2, 3) | Should-NotContainCollection 1 } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected [int] 1 to not be present in collection @(1, 2, 3), but it was there." + } + + It "Passes when collection of multiple items does not contain the expected item" { + @(5, 6, 7) | Should-NotContainCollection 1 + } + + It "Can be called with positional parameters" { + { Should-NotContainCollection 1 1, 2, 3 } | Verify-AssertionFailed + } + } +} diff --git a/tst/functions/assert/Common/Collect-Input.Tests.ps1 b/tst/functions/assert/Common/Collect-Input.Tests.ps1 new file mode 100644 index 000000000..98bc322c4 --- /dev/null +++ b/tst/functions/assert/Common/Collect-Input.Tests.ps1 @@ -0,0 +1,111 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Collect-Input" { + BeforeAll { + function Assert-PassThru { + # This is how all Assert-* functions look inside, here we just collect $Actual and return it. + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] + param ( + [Parameter(ValueFromPipeline = $true)] + $Actual, + [switch] $UnrollInput + ) + + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput:$UnrollInput + $collectedInput + } + } + + Describe "Pipeline input" { + It "Given `$null through pipeline when unrolling it captures `$null" { + $collectedInput = $null | Assert-PassThru -UnrollInput + + Verify-True $collectedInput.IsPipelineInput + if ($null -ne $collectedInput.Actual) { + throw "Expected `$null, but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + + It "Given `$null through pipeline it captures @(`$null)" { + $collectedInput = $null | Assert-PassThru -UnrollInput + + Verify-True $collectedInput.IsPipelineInput + if ($null -ne $collectedInput.Actual) { + throw "Expected `$null, but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + + It "Given @() through pipeline it captures @()" { + $collectedInput = @() | Assert-PassThru + + Verify-True $collectedInput.IsPipelineInput + Verify-Type -Actual $collectedInput.Actual -Expected ([Object[]]) + if (@() -ne $collectedInput.Actual) { + throw "Expected @(), but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + + It "Given List[int] through pipeline it captures the items in Object[]" { + $collectedInput = [Collections.Generic.List[int]]@(1, 2) | Assert-PassThru + + Verify-True $collectedInput.IsPipelineInput + Verify-Type -Actual $collectedInput.Actual -Expected ([Object[]]) + if (1 -ne $collectedInput.Actual[0] -or 2 -ne $collectedInput.Actual[1]) { + throw "Expected @(1, 2), but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + + It "Given 1,2 through pipeline it captures the items" { + $collectedInput = 1, 2 | Assert-PassThru + + Verify-True $collectedInput.IsPipelineInput + Verify-Type -Actual $collectedInput.Actual -Expected ([Object[]]) + if (1 -ne $collectedInput.Actual[0] -or 2 -ne $collectedInput.Actual[1]) { + throw "Expected @(1, 2), but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + } + + Describe "Parameter input" { + It "Given `$null through parameter it captures `$null" { + $collectedInput = Assert-PassThru -Actual $null + + Verify-False $collectedInput.IsPipelineInput + if ($null -ne $collectedInput.Actual) { + throw "Expected `$null, but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + + It "Given @() through parameter it captures @()" { + $collectedInput = Assert-PassThru -Actual @() + + Verify-False $collectedInput.IsPipelineInput + Verify-Type -Actual $collectedInput.Actual -Expected ([Object[]]) + if (@() -ne $collectedInput.Actual) { + throw "Expected @(), but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + + It "Given List[int] through parameter it captures the List" { + $collectedInput = Assert-PassThru -Actual ([Collections.Generic.List[int]]@(1, 2)) + + Verify-False $collectedInput.IsPipelineInput + Verify-Type -Actual $collectedInput.Actual -Expected ([Collections.Generic.List[int]]) + if (1 -ne $collectedInput.Actual[0] -or 2 -ne $collectedInput.Actual[1]) { + throw "Expected List(1, 2), but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + + It "Given 1,2 through parameter it captures the items" { + $collectedInput = Assert-PassThru -Actual 1, 2 + + Verify-False $collectedInput.IsPipelineInput + Verify-Type -Actual $collectedInput.Actual -Expected ([Object[]]) + if (1 -ne $collectedInput.Actual[0] -or 2 -ne $collectedInput.Actual[1]) { + throw "Expected @(1, 2), but got $(Format-Nicely2 $collectedInput.Actual)." + } + } + } + } +} diff --git a/tst/functions/assert/Common/Ensure-ExpectedIsNotCollection.Tests.ps1 b/tst/functions/assert/Common/Ensure-ExpectedIsNotCollection.Tests.ps1 new file mode 100644 index 000000000..93ee5ecd5 --- /dev/null +++ b/tst/functions/assert/Common/Ensure-ExpectedIsNotCollection.Tests.ps1 @@ -0,0 +1,20 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Ensure-ExpectedIsNotCollection" { + It "Given a collection it throws ArgumentException" { + $err = { Ensure-ExpectedIsNotCollection -InputObject @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } + + It "Given a collection it throws correct message" { + $err = { Ensure-ExpectedIsNotCollection -InputObject @() } | Verify-Throw + $err.Exception.Message | Verify-Equal 'You provided a collection to the -Expected parameter. Using a collection on the -Expected side is not allowed by this assertion, because it leads to unexpected behavior. Please use Should-Any, Should-All or some other specialized collection assertion.' + } + + + It "Given a value it passes it to output when it is not a collection" { + Ensure-ExpectedIsNotCollection -InputObject 'a' | Verify-Equal 'a' + } + } +} diff --git a/tst/functions/assert/Common/Get-AssertionMessage.Tests.ps1 b/tst/functions/assert/Common/Get-AssertionMessage.Tests.ps1 new file mode 100644 index 000000000..22936417c --- /dev/null +++ b/tst/functions/assert/Common/Get-AssertionMessage.Tests.ps1 @@ -0,0 +1,53 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Get-AssertionMessage" { + It "returns correct message when no tokens are provided" { + $expected = "Static failure message." + $customMessage = "Static failure message." + Get-AssertionMessage -CustomMessage $customMessage -Expected 1 -Actual 2 | Verify-Equal $expected + } + + It "returns correct message when named tokens are provided" { + $expected = "We expected string to be 1, but got 2." + $customMessage = "We expected string to be , but got ." + Get-AssertionMessage -CustomMessage $customMessage -Expected 1 -Actual 2 | Verify-Equal $expected + } + + It "returns correct message when complex objects are provided" { + $expected = "We expected string to be PSObject{Age=28; Name='Jakub'}, but got 2." + $customMessage = "We expected string to be , but got ." + Get-AssertionMessage -CustomMessage $customMessage -Expected ([PSCustomObject]@{Name = 'Jakub'; Age = 28 }) -Actual 2 | Verify-Equal $expected + } + + It "returns correct message when type tokens are provided" { + $expected = "We expected string to be [PSObject], but got [int]." + $customMessage = "We expected string to be , but got ." + Get-AssertionMessage -CustomMessage $customMessage -Expected ([PSCustomObject]@{Name = 'Jakub'; Age = 28 }) -Actual 2 | Verify-Equal $expected + } + + It "returns correct type message when `$null is provided" { + $expected = "Expected type is [null], and actual type is [null]." + $customMessage = "Expected type is , and actual type is ." + Get-AssertionMessage -CustomMessage $customMessage -Expected $null -Actual $null | Verify-Equal $expected + } + + It "returns correct message when option is provided" { + $expected = "Expected 'a', but got 'b'. Used options: CaseSensitive, IgnoreWhitespace." + $customMessage = "Expected 'a', but got 'b'. " + Get-AssertionMessage -CustomMessage $customMessage -Expected 'a' -Actual 'b' -Option "CaseSensitive", "IgnoreWhitespace" | Verify-Equal $expected + } + + It "returns correct message when additional data are provided" { + $expected = "but 3 of them '@(1, 2, 3)' did not pass the filter." + + $customMessage = "but of them '' did not pass the filter." + $data = @{ + actualFilteredCount = 3 + actualFiltered = 1, 2, 3 + } + + Get-AssertionMessage -CustomMessage $customMessage -Data $data | Verify-Equal $expected + } + } +} diff --git a/tst/functions/assert/Equivalence/Should-BeEquivalent.Options.Tests.ps1 b/tst/functions/assert/Equivalence/Should-BeEquivalent.Options.Tests.ps1 new file mode 100644 index 000000000..2a14c79d7 --- /dev/null +++ b/tst/functions/assert/Equivalence/Should-BeEquivalent.Options.Tests.ps1 @@ -0,0 +1,382 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Compare-Equivalent - Exclude path options" { + Context "Full excluded paths" { + + It "Given a full path to a property it ignores it on the Expected object" -TestCases @( + @{ Path = $null } + @{ Path = "ParentProperty1" } + @{ Path = "ParentProperty1.ParentProperty2" } + ) { + param ($Path) + + $expected = [PSCustomObject]@{ + Name = "Jakub" + Age = 30 + } + + $actual = [PSCustomObject]@{ + Name = "Jakub" + } + + $options = Get-EquivalencyOption -ExcludePath ("$Path.Age".Trim('.')) + Compare-Equivalent -Actual $actual -Expected $expected -Path $Path -Options $options | Verify-Null + } + + It "Given a full path to a property it ignores it on the Actual object" -TestCases @( + @{ Path = $null } + @{ Path = "ParentProperty1" } + @{ Path = "ParentProperty1.ParentProperty2" } + ) { + param ($Path) + $expected = [PSCustomObject]@{ + Name = "Jakub" + } + + $actual = [PSCustomObject]@{ + Name = "Jakub" + Age = 30 + } + + $options = Get-EquivalencyOption -ExcludePath ("$Path.Age".Trim('.')) + Compare-Equivalent -Actual $actual -Expected $expected -Path $Path -Options $options | Verify-Null + } + + + It "Given a full path to a property on object that is in collection it ignores it on the Expected object" { + $expected = [PSCustomObject]@{ + ProgrammingLanguages = @( + ([PSCustomObject]@{ + Name = "C#" + Type = "OO" + }), + ([PSCustomObject]@{ + Name = "PowerShell" + }) + ) + } + + $actual = [PSCustomObject]@{ + ProgrammingLanguages = @( + ([PSCustomObject]@{ + Name = "C#" + }), + ([PSCustomObject]@{ + Name = "PowerShell" + }) + ) + } + + + $options = Get-EquivalencyOption -ExcludePath "ProgrammingLanguages.Type" + Compare-Equivalent -Actual $actual -Expected $expected -Options $options | Verify-Null + } + + It "Given a full path to a property on object that is in collection it ignores it on the Actual object" { + $expected = [PSCustomObject]@{ + ProgrammingLanguages = @( + ([PSCustomObject]@{ + Name = "C#" + }), + ([PSCustomObject]@{ + Name = "PowerShell" + }) + ) + } + + $actual = [PSCustomObject]@{ + ProgrammingLanguages = @( + ([PSCustomObject]@{ + Name = "C#" + Type = "OO" + }), + ([PSCustomObject]@{ + Name = "PowerShell" + }) + ) + } + + + $options = Get-EquivalencyOption -ExcludePath "ProgrammingLanguages.Type" + Compare-Equivalent -Actual $actual -Expected $expected -Options $options | Verify-Null + } + + It "Given a full path to a property on object that is in hashtable it ignores it on the Expected object" { + $expected = [PSCustomObject]@{ + ProgrammingLanguages = @{ + Language1 = ([PSCustomObject]@{ + Name = "C#" + Type = "OO" + }); + Language2 = ([PSCustomObject]@{ + Name = "PowerShell" + }) + } + } + + $actual = [PSCustomObject]@{ + ProgrammingLanguages = @{ + Language1 = ([PSCustomObject]@{ + Name = "C#" + }); + Language2 = ([PSCustomObject]@{ + Name = "PowerShell" + }) + } + } + + $options = Get-EquivalencyOption -ExcludePath "ProgrammingLanguages.Language1.Type" + Compare-Equivalent -Actual $actual -Expected $expected -Options $options | Verify-Null + } + + # in the above tests we are not testing all the possible options of skippin in all possible + # emumerable objects, but this many tests should still be enough. The Path unifies how different + # collections are handled, and we filter out based on the path on the start of Compare-Equivalent + # so the same rules should apply transitively no matter the collection type + + + It "Given a full path to a key on a hashtable it ignores it on the Expected hashtable" { + $expected = @{ + Name = "C#" + Type = "OO" + } + + $actual = @{ + Name = "C#" + } + + $options = Get-EquivalencyOption -ExcludePath "Type" + Compare-Equivalent -Actual $actual -Expected $expected -Options $options | Verify-Null + } + + It "Given a full path to a key on a hashtable it ignores it on the Actual hashtable" { + $expected = @{ + Name = "C#" + } + + $actual = @{ + Name = "C#" + Type = "OO" + } + + $options = Get-EquivalencyOption -ExcludePath "Type" + Compare-Equivalent -Actual $actual -Expected $expected -Options $options | Verify-Null + } + + It "Given a full path to a key on a dictionary it ignores it on the Expected dictionary" { + $expected = New-Dictionary @{ + Name = "C#" + Type = "OO" + } + + $actual = New-Dictionary @{ + Name = "C#" + } + + $options = Get-EquivalencyOption -ExcludePath "Type" + Compare-Equivalent -Actual $actual -Expected $expected -Options $options | Verify-Null + } + + It "Given a full path to a key on a dictionary it ignores it on the Actual dictionary" { + $expected = New-Dictionary @{ + Name = "C#" + } + + $actual = New-Dictionary @{ + Name = "C#" + Type = "OO" + } + + $options = Get-EquivalencyOption -ExcludePath "Type" + Compare-Equivalent -Actual $actual -Expected $expected -Options $options | Verify-Null + } + + It "Given options it passes them correctly from Should-BeEquivalent" { + $expected = [PSCustomObject]@{ + Name = "Jakub" + Location = "Prague" + Age = 30 + } + + $actual = [PSCustomObject]@{ + Name = "Jakub" + } + + $options = Get-EquivalencyOption -ExcludePath "Age", "NonExisting" + $err = { Should-BeEquivalent -Actual $actual -Expected $expected -Options $options } | Verify-AssertionFailed + + $err.Exception.Message | Verify-Like "*Expected has property 'Location'*" + $err.Exception.Message | Verify-Like "*Exclude path 'Age'*" + } + } + + Context "Wildcard path exclusions" { + It "Given wildcarded path it ignores it on the expected object" { + $expected = [PSCustomObject] @{ + Name = "Jakub" + Location = "Prague" + } + + $actual = [PSCustomObject] @{ + Name = "Jakub" + } + + $options = Get-EquivalencyOption -ExcludePath Loc* + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + + It "Given wildcarded path it ignores it on the actual object" { + $expected = [PSCustomObject] @{ + Name = "Jakub" + } + + $actual = [PSCustomObject] @{ + Name = "Jakub" + Location = "Prague" + } + + $options = Get-EquivalencyOption -ExcludePath Loc* + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + + It "Given wildcarded path it ignores it on the expected hashtable" { + $expected = @{ + Name = "Jakub" + Location = "Prague" + } + + $actual = @{ + Name = "Jakub" + } + + $options = Get-EquivalencyOption -ExcludePath Loc* + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + + It "Given wildcarded path it ignores it on the actual hashtable" { + $expected = @{ + Name = "Jakub" + } + + $actual = @{ + Name = "Jakub" + Location = "Prague" + } + + $options = Get-EquivalencyOption -ExcludePath Loc* + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + + It "Given wildcarded path it ignores it on the expected dictionary" { + $expected = New-Dictionary @{ + Name = "Jakub" + Location = "Prague" + } + + $actual = New-Dictionary @{ + Name = "Jakub" + } + + $options = Get-EquivalencyOption -ExcludePath Loc* + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + + It "Given wildcarded path it ignores it on the actual dictionary" { + $expected = New-Dictionary @{ + Name = "Jakub" + } + + $actual = New-Dictionary @{ + Name = "Jakub" + Location = "Prague" + } + + $options = Get-EquivalencyOption -ExcludePath Loc* + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + } + + Context "-ExcludePathsNotOnExpected" { + It "Given actual object that has more properties that expected it skips them" { + $expected = [PSCustomObject] @{ + Name = "Jakub" + } + + $actual = [PSCustomObject] @{ + Name = "Jakub" + Location = "Prague" + Age = 30 + } + + $options = Get-EquivalencyOption -ExcludePathsNotOnExpected + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + + It "Given actual hashtable that has more keys that expected it skips them" { + $expected = @{ + Name = "Jakub" + } + + $actual = @{ + Name = "Jakub" + Location = "Prague" + Age = 30 + } + + $options = Get-EquivalencyOption -ExcludePathsNotOnExpected + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + + It "Given actual dictionary that has more keys that expected it skips them" { + $expected = New-Dictionary @{ + Name = "Jakub" + } + + $actual = New-Dictionary @{ + Name = "Jakub" + Location = "Prague" + Age = 30 + } + + $options = Get-EquivalencyOption -ExcludePathsNotOnExpected + Should-BeEquivalent -Actual $actual -Expected $expected -Options $Options + } + } + } + + Describe "Compare-Equiavlent - equality comparison options" { + It "Given objects that are equivalent and -Comparator Equality option it compares them as different" { + $expected = [PSCustomObject]@{ + LikesIfsInMocks = $false + } + + $actual = [PSCustomObject]@{ + LikesIfsInMocks = "False" + } + + $options = Get-EquivalencyOption -Comparator Equality + { Should-BeEquivalent -Actual $actual -Expected $expected -Options $options } | Verify-AssertionFailed + } + } + + + Describe "Printing Options into difference report" { + + It "Given options that exclude property it shows up in the difference report correctly" { + $options = Get-EquivalencyOption -ExcludePath "Age", "Name", "Person.Age", "Person.Created*" + Clear-WhiteSpace (Format-EquivalencyOptions -Options $options) | Verify-Equal (Clear-WhiteSpace " + Exclude path 'Age' + Exclude path 'Name' + Exclude path 'Person.Age' + Exclude path 'Person.Created*'") + } + + It "Given options that exclude property it shows up in the difference report correctly" { + $options = Get-EquivalencyOption -ExcludePathsNotOnExpected + Clear-WhiteSpace (Format-EquivalencyOptions -Options $options) | Verify-Equal (Clear-WhiteSpace " + Excluding all paths not found on Expected") + } + } + +} diff --git a/tst/functions/assert/Equivalence/Should-BeEquivalent.Tests.ps1 b/tst/functions/assert/Equivalence/Should-BeEquivalent.Tests.ps1 new file mode 100644 index 000000000..3a280638a --- /dev/null +++ b/tst/functions/assert/Equivalence/Should-BeEquivalent.Tests.ps1 @@ -0,0 +1,483 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + BeforeDiscovery { + Add-Type -TypeDefinition 'namespace Assertions.TestType { + public class Person2 { + // powershell v2 mandates fully implemented properties + string _name; + int _age; + public string Name { get { return _name; } set { _name = value; } } + public int Age { get { return _age; } set { _age = value; } } + } + }' + } + + BeforeAll { + function Get-TestCase ($Value) { + #let's see if this is useful, it's nice for values, but sucks for + #types that serialize to just the type name (most of them) + if ($null -ne $Value) { + @{ + Value = $Value + Type = $Value.GetType() + } + } + else { + @{ + Value = $null + Type = '' + } + } + } + } + + Describe "Test-Same" { + It "Given the same instance of a reference type it returns `$true" -TestCases @( + @{ Value = $null }, + @{ Value = @() }, + @{ Value = [Type] }, + @{ Value = (New-Object -TypeName Diagnostics.Process) } + ) { + param($Value) + Test-Same -Expected $Value -Actual $Value | Verify-True + } + + It "Given different instances of a reference type it returns `$false" -TestCases @( + @{ Actual = @(); Expected = @() }, + @{ Actual = (New-Object -TypeName Diagnostics.Process) ; Expected = (New-Object -TypeName Diagnostics.Process) } + ) { + param($Expected, $Actual) + Test-Same -Expected $Expected -Actual $Actual | Verify-False + } + } + + Describe "Get-TestCase" { + It "Given a value it returns the value and its type in a hashtable" { + $expected = @{ + Value = 1 + Type = [Int] + } + + $actual = Get-TestCase -Value $expected.Value + + $actual.GetType().Name | Verify-Equal 'hashtable' + $actual.Value | Verify-Equal $expected.Value + $actual.Type | Verify-Equal $expected.Type + } + + It "Given `$null it returns as the name of the type" { + $expected = @{ + Value = $null + Type = 'none' + } + + $actual = Get-TestCase -Value $expected.Value + + $actual.GetType().Name | Verify-Equal 'hashtable' + $actual.Value | Verify-Null + $actual.Type | Verify-Equal '' + } + } + + Describe "Get-ValueNotEquivalentMessage" { + It "Returns correct message when comparing value to an object" { + $e = 'abc' + $a = [PSCustomObject]@{ Name = 'Jakub'; Age = 28 } + Get-ValueNotEquivalentMessage -Actual $a -Expected $e | + Verify-Equal "Expected 'abc' to be equivalent to the actual value, but got PSObject{Age=28; Name='Jakub'}." + } + + It "Returns correct message when comparing object to a value" { + $e = [PSCustomObject]@{ Name = 'Jakub'; Age = 28 } + $a = 'abc' + Get-ValueNotEquivalentMessage -Actual $a -Expected $e | + Verify-Equal "Expected PSObject{Age=28; Name='Jakub'} to be equivalent to the actual value, but got 'abc'." + } + + It "Returns correct message when comparing value to an array" { + $e = 'abc' + $a = 1, 2, 3 + Get-ValueNotEquivalentMessage -Actual $a -Expected $e | + Verify-Equal "Expected 'abc' to be equivalent to the actual value, but got @(1, 2, 3)." + } + + It "Returns correct message when comparing value to null" { + $e = 'abc' + $a = $null + Get-ValueNotEquivalentMessage -Actual $a -Expected $e | + Verify-Equal "Expected 'abc' to be equivalent to the actual value, but got `$null." + } + + It "Returns correct message for given property" { + $e = 1 + $a = 2 + Get-ValueNotEquivalentMessage -Actual 1 -Expected 2 -Property ".Age" | + Verify-Equal "Expected property .Age with value 2 to be equivalent to the actual value, but got 1." + } + + It "Changes wording to 'equal' when options specify Equality comparator" { + $e = 1 + $a = 2 + $options = Get-EquivalencyOption -Comparator Equality + Get-ValueNotEquivalentMessage -Actual 1 -Expected 2 -Options $options | + Verify-Equal "Expected 2 to be equal to the actual value, but got 1." + } + } + + Describe "Is-CollectionSize" { + It "Given two collections '' '' of the same size it returns `$true" -TestCases @( + @{ Actual = (1, 2, 3); Expected = (1, 2, 3) }, + @{ Actual = (1, 2, 3); Expected = (3, 2, 1) } + ) { + param ($Actual, $Expected) + Is-CollectionSize -Actual $Actual -Expected $Expected | Verify-True + } + + It "Given two collections '' '' of different sizes it returns `$false" -TestCases @( + @{ Actual = (1, 2, 3); Expected = (1, 2, 3, 4) }, + @{ Actual = (1, 2, 3); Expected = (1, 2) } + @{ Actual = (1, 2, 3); Expected = @() } + ) { + param ($Actual, $Expected) + Is-CollectionSize -Actual $Actual -Expected $Expected | Verify-False + } + } + + Describe "Get-CollectionSizeNotTheSameMessage" { + It "Given two collections of differrent sizes it returns the correct message" { + Get-CollectionSizeNotTheSameMessage -Expected (1, 2, 3) -Actual (1, 2) | Verify-Equal "Expected collection @(1, 2, 3) with length 3 to be the same size as the actual collection, but got @(1, 2) with length 2." + } + } + + Describe "Compare-ValueEquivalent" { + It "Given expected that is not a value it throws ArgumentException" { + $err = { Compare-ValueEquivalent -Actual "dummy" -Expected (Get-Process -Id $PID) } | Verify-Throw + $err.Exception -is [ArgumentException] | Verify-True + } + + It "Given values '' and '' that are not equivalent it returns message ''." -TestCases @( + @{ Actual = $null; Expected = 1; Message = "Expected 1 to be equivalent to the actual value, but got `$null." }, + @{ Actual = $null; Expected = ""; Message = "Expected to be equivalent to the actual value, but got `$null." }, + @{ Actual = $true; Expected = $false; Message = "Expected `$false to be equivalent to the actual value, but got `$true." }, + @{ Actual = $true; Expected = 'False'; Message = "Expected `$false to be equivalent to the actual value, but got `$true." }, + @{ Actual = 1; Expected = -1; Message = "Expected -1 to be equivalent to the actual value, but got 1." }, + @{ Actual = "1"; Expected = 1.01; Message = "Expected 1.01 to be equivalent to the actual value, but got '1'." }, + @{ Actual = "abc"; Expected = "a b c"; Message = "Expected 'a b c' to be equivalent to the actual value, but got 'abc'." }, + @{ Actual = @("abc", "bde"); Expected = "abc"; Message = "Expected 'abc' to be equivalent to the actual value, but got @('abc', 'bde')." }, + @{ Actual = { def }; Expected = "abc"; Message = "Expected 'abc' to be equivalent to the actual value, but got { def }." }, + @{ Actual = ([PSCustomObject]@{ Name = 'Jakub' }); Expected = "abc"; Message = "Expected 'abc' to be equivalent to the actual value, but got PSObject{Name='Jakub'}." }, + @{ Actual = (1, 2, 3); Expected = "abc"; Message = "Expected 'abc' to be equivalent to the actual value, but got @(1, 2, 3)." } + ) { + param($Actual, $Expected, $Message) + Compare-ValueEquivalent -Actual $Actual -Expected $Expected | Verify-Equal $Message + } + } + + Describe "Compare-CollectionEquivalent" { + It "Given expected that is not a collection it throws ArgumentException" { + $err = { Compare-CollectionEquivalent -Actual "dummy" -Expected 1 } | Verify-Throw + $err.Exception -is [ArgumentException] | Verify-True + } + + It "Given two collections '' '' of different sizes it returns message ''" -TestCases @( + @{ Actual = (1, 2, 3); Expected = (1, 2, 3, 4); Message = "Expected collection @(1, 2, 3, 4) with length 4 to be the same size as the actual collection, but got @(1, 2, 3) with length 3." }, + @{ Actual = (1, 2, 3); Expected = (3, 1); Message = "Expected collection @(3, 1) with length 2 to be the same size as the actual collection, but got @(1, 2, 3) with length 3." } + ) { + param ($Actual, $Expected, $Message) + Compare-CollectionEquivalent -Actual $Actual -Expected $Expected | Verify-Equal $Message + } + + It "Given collection '' on the expected side and non-collection '' on the actual side it prints the correct message ''" -TestCases @( + @{ Actual = 3; Expected = (1, 2, 3, 4); Message = "Expected collection @(1, 2, 3, 4) with length 4, but got 3." }, + @{ Actual = ([PSCustomObject]@{ Name = 'Jakub' }); Expected = (1, 2, 3, 4); Message = "Expected collection @(1, 2, 3, 4) with length 4, but got PSObject{Name='Jakub'}." } + ) { + param ($Actual, $Expected, $Message) + Compare-CollectionEquivalent -Actual $Actual -Expected $Expected | Verify-Equal $Message + } + + It "Given two collections '' '' it compares each value with each value and returns `$null if all of them are equivalent" -TestCases @( + @{ Actual = (1, 2, 3); Expected = (1, 2, 3) } + @{ Actual = (1, 2, 3); Expected = (3, 2, 1) } + + # issue https://github.com/nohwnd/Assert/issues/31 + @{ Actual = ($null, $null); Expected = ($null, $null) } + @{ Actual = ($null, $null, $null); Expected = ($null, $null, $null) } + @{ Actual = (1, 1, 1, 1); Expected = (1, 1, 1, 1) } + @{ Actual = (1, 2, 2, 1); Expected = (2, 1, 2, 1) } + ## + + ) { + param ($Actual, $Expected) + Compare-CollectionEquivalent -Actual $Actual -Expected $Expected | Verify-Null + } + + It "Given two collections '' '' it compares each value with each value and returns message ' if any of them are not equivalent" -TestCases @( + @{ Actual = (1, 2, 3); Expected = (4, 5, 6); Message = "Expected collection @(4, 5, 6) to be equivalent to @(1, 2, 3) but some values were missing: @(4, 5, 6)." }, + @{ Actual = (1, 2, 3); Expected = (1, 2, 2); Message = "Expected collection @(1, 2, 2) to be equivalent to @(1, 2, 3) but some values were missing: 2." } + ) { + param ($Actual, $Expected, $Message) + Compare-CollectionEquivalent -Actual $Actual -Expected $Expected | Verify-Equal $Message + } + } + + Describe "Compare-ObjectEquivalent" { + It "Given expected '' that is not an object it throws ArgumentException" -TestCases @( + @{ Expected = "a" }, + @{ Expected = "1" }, + @{ Expected = { abc } }, + @{ Expected = (1, 2, 3) } + ) { + param($Expected) {} + $err = { Compare-ObjectEquivalent -Actual "dummy" -Expected $Expected } | Verify-Throw + $err.Exception -is [ArgumentException] | Verify-True + } + + It "Given values '' and '' that are not equivalent it returns message ''." -TestCases @( + @{ Actual = 'a'; Expected = ([PSCustomObject]@{ Name = 'Jakub' }); Message = "Expected object PSObject{Name='Jakub'}, but got 'a'." } + ) { + param ($Actual, $Expected, $Message) + Compare-ObjectEquivalent -Expected $Expected -Actual $Actual | Verify-Equal $Message + } + } + + Describe "Compare-HashtableEquivalent" { + It "Given expected '' that is not a hashtable it throws ArgumentException" -TestCases @( + @{ Expected = "a" } + ) { + param($Expected) {} + $err = { Compare-HashtableEquivalent -Actual "dummy" -Expected $Expected } | Verify-Throw + $err.Exception -is [ArgumentException] | Verify-True + } + + It "Given values '' and '' that are not equivalent it returns message ''." -TestCases @( + @{ Actual = 'a'; Expected = @{ Name = 'Jakub' }; Message = "Expected hashtable @{Name='Jakub'}, but got 'a'." } + @{ Actual = @{ }; Expected = @{ Name = 'Jakub' }; Message = "Expected hashtable @{Name='Jakub'}, but got @{}.`nExpected has key 'Name' that the other object does not have." } + @{ Actual = @{ Name = 'Tomas' }; Expected = @{ Name = 'Jakub' }; Message = "Expected hashtable @{Name='Jakub'}, but got @{Name='Tomas'}.`nExpected property .Name with value 'Jakub' to be equivalent to the actual value, but got 'Tomas'." } + @{ Actual = @{ Name = 'Tomas'; Value = 10 }; Expected = @{ Name = 'Jakub' }; Message = "Expected hashtable @{Name='Jakub'}, but got @{Name='Tomas'; Value=10}.`nExpected property .Name with value 'Jakub' to be equivalent to the actual value, but got 'Tomas'.`nExpected is missing key 'Value' that the other object has." } + ) { + param ($Actual, $Expected, $Message) + + Compare-HashtableEquivalent -Expected $Expected -Actual $Actual | Verify-Equal $Message + } + } + + Describe "Compare-DictionaryEquivalent" { + It "Given expected '' that is not a dictionary it throws ArgumentException" -TestCases @( + @{ Expected = "a" } + ) { + param($Expected) {} + $err = { Compare-DictionaryEquivalent -Actual "dummy" -Expected $Expected } | Verify-Throw + $err.Exception -is [ArgumentException] | Verify-True + } + + It "Given values '' and '' that are not equivalent it returns message ''." -TestCases @( + @{ Actual = 'a'; Expected = New-Dictionary @{ Name = 'Jakub' }; Message = "Expected dictionary Dictionary{Name='Jakub'}, but got 'a'." } + @{ Actual = New-Dictionary @{ }; Expected = New-Dictionary @{ Name = 'Jakub' }; Message = "Expected dictionary Dictionary{Name='Jakub'}, but got Dictionary{}.`nExpected has key 'Name' that the other object does not have." } + @{ Actual = New-Dictionary @{ Name = 'Tomas' }; Expected = New-Dictionary @{ Name = 'Jakub' }; Message = "Expected dictionary Dictionary{Name='Jakub'}, but got Dictionary{Name='Tomas'}.`nExpected property .Name with value 'Jakub' to be equivalent to the actual value, but got 'Tomas'." } + @{ Actual = New-Dictionary @{ Name = 'Tomas'; Value = 10 }; Expected = New-Dictionary @{ Name = 'Jakub' }; Message = "Expected dictionary Dictionary{Name='Jakub'}, but got Dictionary{Name='Tomas'; Value=10}.`nExpected property .Name with value 'Jakub' to be equivalent to the actual value, but got 'Tomas'.`nExpected is missing key 'Value' that the other object has." } + ) { + param ($Actual, $Expected, $Message) + + Compare-DictionaryEquivalent -Expected $Expected -Actual $Actual | Verify-Equal $Message + } + } + + Describe "Compare-Equivalent" { + It "Given values '' and '' that are equivalent returns report with Equivalent set to `$true" -TestCases @( + @{ Actual = $null; Expected = $null }, + @{ Actual = ""; Expected = "" }, + @{ Actual = $true; Expected = $true }, + @{ Actual = $true; Expected = 'True' }, + @{ Actual = 'True'; Expected = $true }, + @{ Actual = $false; Expected = 'False' }, + @{ Actual = 'False'; Expected = $false }, + @{ Actual = 1; Expected = 1 }, + @{ Actual = "1"; Expected = 1 }, + @{ Actual = "abc"; Expected = "abc" }, + @{ Actual = @("abc"); Expected = "abc" }, + @{ Actual = "abc"; Expected = @("abc") }, + @{ Actual = { abc }; Expected = " abc " }, + @{ Actual = " abc "; Expected = { abc } }, + @{ Actual = { abc }; Expected = { abc } } + ) { + param ($Actual, $Expected) + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Null + } + + It "Given values '' and '' that are not equivalent it returns message ''." -TestCases @( + @{ Actual = $null; Expected = 1; Message = "Expected 1 to be equivalent to the actual value, but got `$null." }, + @{ Actual = $null; Expected = ""; Message = "Expected to be equivalent to the actual value, but got `$null." }, + @{ Actual = $true; Expected = $false; Message = "Expected `$false to be equivalent to the actual value, but got `$true." }, + @{ Actual = $true; Expected = 'False'; Message = "Expected `$false to be equivalent to the actual value, but got `$true." }, + @{ Actual = 1; Expected = -1; Message = "Expected -1 to be equivalent to the actual value, but got 1." }, + @{ Actual = "1"; Expected = 1.01; Message = "Expected 1.01 to be equivalent to the actual value, but got '1'." }, + @{ Actual = "abc"; Expected = "a b c"; Message = "Expected 'a b c' to be equivalent to the actual value, but got 'abc'." }, + @{ Actual = @("abc", "bde"); Expected = "abc"; Message = "Expected 'abc' to be equivalent to the actual value, but got @('abc', 'bde')." }, + @{ Actual = { def }; Expected = "abc"; Message = "Expected 'abc' to be equivalent to the actual value, but got { def }." }, + @{ Actual = "def"; Expected = { abc }; Message = "Expected { abc } to be equivalent to the actual value, but got 'def'." }, + @{ Actual = { abc }; Expected = { def }; Message = "Expected { def } to be equivalent to the actual value, but got { abc }." }, + @{ Actual = (1, 2, 3); Expected = (1, 2, 3, 4); Message = "Expected collection @(1, 2, 3, 4) with length 4 to be the same size as the actual collection, but got @(1, 2, 3) with length 3." }, + @{ Actual = 3; Expected = (1, 2, 3, 4); Message = "Expected collection @(1, 2, 3, 4) with length 4, but got 3." }, + @{ Actual = ([PSCustomObject]@{ Name = 'Jakub' }); Expected = (1, 2, 3, 4); Message = "Expected collection @(1, 2, 3, 4) with length 4, but got PSObject{Name='Jakub'}." }, + @{ Actual = ([PSCustomObject]@{ Name = 'Jakub' }); Expected = "a"; Message = "Expected 'a' to be equivalent to the actual value, but got PSObject{Name='Jakub'}." }, + @{ Actual = 'a'; Expected = ([PSCustomObject]@{ Name = 'Jakub' }); Message = "Expected object PSObject{Name='Jakub'}, but got 'a'." } + @{ Actual = 'a'; Expected = @{ Name = 'Jakub' }; Message = "Expected hashtable @{Name='Jakub'}, but got 'a'." } + @{ Actual = 'a'; Expected = New-Dictionary @{ Name = 'Jakub' }; Message = "Expected dictionary Dictionary{Name='Jakub'}, but got 'a'." } + ) { + param ($Actual, $Expected, $Message) + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Equal $Message + } + + It "Comparing the same instance of a psObject returns null" { + $actual = $expected = [PSCustomObject]@{ Name = 'Jakub' } + Verify-Same -Expected $expected -Actual $actual + + Compare-Equivalent -Expected $expected -Actual $actual | Verify-Null + } + + It "Given PSObjects '' and ' that are different instances but have the same values it returns report with Equivalent set to `$true" -TestCases @( + @{ + Expected = [PSCustomObject]@{ Name = 'Jakub' } + Actual = [PSCustomObject]@{ Name = 'Jakub' } + }, + @{ + Expected = [PSCustomObject]@{ Name = 'Jakub' } + Actual = [PSCustomObject]@{ Name = 'Jakub' } + }, + @{ + Expected = [PSCustomObject]@{ Age = 28 } + Actual = [PSCustomObject]@{ Age = '28' } + } + ) { + param ($Expected, $Actual) + Verify-NotSame -Expected $Expected -Actual $Actual + + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Null + } + + It "Given PSObjects '' and ' that have different values in some of the properties it returns message ''" -TestCases @( + @{ + Expected = [PSCustomObject]@{ Name = 'Jakub'; Age = 28 } + Actual = [PSCustomObject]@{ Name = 'Jakub'; Age = 19 } + Message = "Expected property .Age with value 28 to be equivalent to the actual value, but got 19." + }, + @{ + Expected = [PSCustomObject]@{ Name = 'Jakub'; Age = 28 } + Actual = [PSCustomObject]@{ Name = 'Jakub' } + Message = "Expected has property 'Age' that the other object does not have." + }, + @{ + Expected = [PSCustomObject]@{ Name = 'Jakub' } + Actual = [PSCustomObject]@{ Name = 'Jakub'; Age = 28 } + Message = "Expected is missing property 'Age' that the other object has." + } + ) { + param ($Expected, $Actual, $Message) + Verify-NotSame -Expected $Expected -Actual $Actual + + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Equal $Message + } + + It "Given PSObject '' and object ' that have the same values it returns `$null" -TestCases @( + @{ + Expected = New-Object -TypeName Assertions.TestType.Person2 -Property @{ Name = 'Jakub'; Age = 28 } + Actual = [PSCustomObject]@{ Name = 'Jakub'; Age = 28 } + } + ) { + param ($Expected, $Actual) + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Null + } + + + It "Given PSObjects '' and ' that contain different arrays in the same property returns the correct message" -TestCases @( + @{ + Expected = [PSCustomObject]@{ Numbers = 1, 2, 3 } + Actual = [PSCustomObject]@{ Numbers = 3, 4, 5 } + } + ) { + param ($Expected, $Actual) + + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Equal "Expected collection in property .Numbers which is @(1, 2, 3) to be equivalent to @(3, 4, 5) but some values were missing: @(1, 2)." + } + + It "Comparing psObjects that have collections of objects returns `$null when the objects have the same value" -TestCases @( + @{ + Expected = [PSCustomObject]@{ Objects = ([PSCustomObject]@{ Name = "Jan" }), ([PSCustomObject]@{ Name = "Tomas" }) } + Actual = [PSCustomObject]@{ Objects = ([PSCustomObject]@{ Name = "Tomas" }), ([PSCustomObject]@{ Name = "Jan" }) } + } + ) { + param ($Expected, $Actual) + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Null + } + + It "Comparing psObjects that have collections of objects returns the correct message when the items in the collection differ" -TestCases @( + @{ + Expected = [PSCustomObject]@{ Objects = ([PSCustomObject]@{ Name = "Jan" }), ([PSCustomObject]@{ Name = "Petr" }) } + Actual = [PSCustomObject]@{ Objects = ([PSCustomObject]@{ Name = "Jan" }), ([PSCustomObject]@{ Name = "Tomas" }) } + } + ) { + param ($Expected, $Actual) + Compare-Equivalent -Expected $Expected -Actual $Actual | Verify-Equal "Expected collection in property .Objects which is @(PSObject{Name='Jan'}, PSObject{Name='Petr'}) to be equivalent to @(PSObject{Name='Jan'}, PSObject{Name='Tomas'}) but some values were missing: @(PSObject{Name='Petr'})." + } + + It "Comparing DataTable" { + # todo: move this to it's own describe, split the tests to smaller parts, and make them use Verify-* axioms + $Expected = New-Object Data.DataTable 'Test' + $null = $Expected.Columns.Add('IDD', [System.Int32]) + $null = $Expected.Columns.Add('Name') + $null = $Expected.Columns.Add('Junk') + $null = $Expected.Columns.Add('IntT', [System.Int32]) + $null = $Expected.Rows.Add(1, 'A', 'AAA', 5) + $null = $Expected.Rows.Add(3, 'C', $null, $null) + + $Actual = New-Object Data.DataTable 'Test' + $null = $Actual.Columns.Add('IDD', [System.Int32]) + $null = $Actual.Columns.Add('Name') + $null = $Actual.Columns.Add('Junk') + $null = $Actual.Columns.Add('IntT', [System.Int32]) + $null = $Actual.Rows.Add(3, 'C', $null, $null) + $null = $Actual.Rows.Add(1, 'A', 'AAA', 5) + + Should-BeEquivalent -Actual $Actual -Expected $Expected + + function SerializeDeserialize ($InputObject) { + # psv2 compatibility + # $ExpectedDeserialized = [System.Management.Automation.PSSerializer]::Deserialize([System.Management.Automation.PSSerializer]::Serialize($Expected)) + # Alternatively this could be done in memory via https://github.com/Jaykul/Reflection/blob/master/CliXml.psm1, but I don't want to fiddle with more + # relfection right now + try { + $path = [IO.Path]::GetTempFileName() + + Export-Clixml -Path $path -InputObject $InputObject -Force | Out-Null + Import-Clixml -Path $path + } + finally { + if ($null -ne $path -and (Test-Path $path)) { + Remove-Item -Path $path -Force + } + } + } + + + $ExpectedDeserialized = SerializeDeserialize $Expected + $ActualDeserialized = SerializeDeserialize $Actual + Should-BeEquivalent -Actual $ActualDeserialized -Expected $ExpectedDeserialized + Should-BeEquivalent -Actual $Actual -Expected $ExpectedDeserialized + + { Should-BeEquivalent -Actual $Actual -Expected $Expected -StrictOrder } | Should -Throw + + $Actual.Rows[1].Name = 'D' + { Should-BeEquivalent -Actual $Actual -Expected $Expected } | Should -Throw + + $ExpectedDeserialized = SerializeDeserialize $Expected + $ActualDeserialized = SerializeDeserialize $Actual + { Should-BeEquivalent -Actual $ActualDeserialized -Expected $ExpectedDeserialized } | Should -Throw + { Should-BeEquivalent -Actual $Actual -Expected $ExpectedDeserialized } | Should -Throw + } + + It "Can be called with positional parameters" { + { Should-BeEquivalent 1 2 } | Verify-AssertionFailed + } + } +} diff --git a/tst/functions/assert/Exception/Should-Throw.Tests.ps1 b/tst/functions/assert/Exception/Should-Throw.Tests.ps1 new file mode 100644 index 000000000..6c8e75856 --- /dev/null +++ b/tst/functions/assert/Exception/Should-Throw.Tests.ps1 @@ -0,0 +1,227 @@ +Set-StrictMode -Version Latest + +Describe "Should-Throw" { + It "Passes when exception is thrown" { + { throw } | Should-Throw + } + + It "Fails when no exception is thrown" { + { { } | Should-Throw } | Verify-AssertionFailed + } + + It "Passes when non-terminating exception is thrown" { + + { Write-Error "fail!" } | Should-Throw + } + + It "Fails when non-terminating exception is thrown and -AllowNonTerminatingError switch is specified" { + { { Write-Error "fail!" } | Should-Throw -AllowNonTerminatingError } | Verify-AssertionFailed + } + + Context "Filtering with exception type" { + It "Passes when exception has the expected type" { + { throw [ArgumentException]"A is null!" } | Should-Throw -ExceptionType ([ArgumentException]) + } + + It "Passes when exception has type that inherits from the expected type" { + { throw [ArgumentNullException]"A is null!" } | Should-Throw -ExceptionType ([ArgumentException]) + } + + It "Fails when exception is thrown, but is not the expected type nor iheriting form the expected type" { + { { throw [InvalidOperationException]"This operation is invalid!" } | Should-Throw -ExceptionType ([ArgumentException]) } | Verify-AssertionFailed + } + } + + Context "Filtering with exception message" { + It "Passes when exception has the expected message" { + { throw [ArgumentException]"A is null!" } | Should-Throw -ExceptionMessage 'A is null!' + } + + It "Fails when exception does not have the expected message" { + { { throw [ArgumentException]"A is null!" } | Should-Throw -ExceptionMessage 'flabbergasted' } | Verify-AssertionFailed + } + + It "Passes when exception has message that matches based on wildcards" { + { throw [ArgumentNullException]"A is null!" } | Should-Throw -ExceptionMessage '*null*' + } + + It "Fails when exception does not match the message with wildcard" { + { { throw [ArgumentException]"A is null!" } | Should-Throw -ExceptionMessage '*flabbergasted*' } | Verify-AssertionFailed + } + } + + Context "Filtering with FullyQualifiedErrorId" { + It "Passes when exception has the FullyQualifiedErrorId" { + { throw [ArgumentException]"A is null!" } | Should-Throw -FullyQualifiedErrorId 'A is null!' + } + + It "Fails when exception does not have the FullyQualifiedErrorId" { + { { throw [ArgumentException]"A is null!" } | Should-Throw -FullyQualifiedErrorId 'flabbergasted' } | Verify-AssertionFailed + } + + It "Passes when exception has FullyQualifiedErrorId that matches based on wildcards" { + { throw [ArgumentNullException]"A is null!" } | Should-Throw -FullyQualifiedErrorId '*null*' + } + + It "Fails when exception does not match the FullyQualifiedErrorId with wildcard" { + { { throw [ArgumentException]"A is null!" } | Should-Throw -FullyQualifiedErrorId '*flabbergasted*' } | Verify-AssertionFailed + } + } + + Context "Verify messages" { + It "Given no exception it returns the correct message" { + $err = { { } | Should-Throw } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal 'Expected an exception, to be thrown, but no exception was thrown.' + } + + It "Given exception that does not match on type it returns the correct message" { + $err = { { throw [ArgumentException]"" } | Should-Throw -ExceptionType ([System.InvalidOperationException]) } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected an exception, of type [InvalidOperationException] to be thrown, but the exception type was [ArgumentException]." + } + + It "Given exception that does not match on message it returns the correct message" { + $err = { { throw [ArgumentException]"fail!" } | Should-Throw -ExceptionMessage 'halt!' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected an exception, with message 'halt!' to be thrown, but the message was 'fail!'." + } + + It "Given exception that does not match on FullyQualifiedErrorId it returns the correct message" { + $err = { { throw [ArgumentException]"SomeId" } | Should-Throw -FullyQualifiedErrorId 'DifferentId' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected an exception, with FullyQualifiedErrorId 'DifferentId' to be thrown, but the FullyQualifiedErrorId was 'SomeId'." + } + + It "Given exception that does not match on type and message it returns the correct message" { + $err = { { throw [ArgumentException]"fail!" } | Should-Throw -ExceptionType ([System.InvalidOperationException]) -ExceptionMessage 'halt!' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected an exception, of type [InvalidOperationException], with message 'halt!' to be thrown, but the exception type was [ArgumentException] and the message was 'fail!'." + } + + It "Given exception that does not match on type and FullyQualifiedErrorId it returns the correct message" { + $err = { { throw [ArgumentException]"SomeId!" } | Should-Throw -ExceptionType ([System.InvalidOperationException]) -FullyQualifiedErrorId 'DifferentId!' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected an exception, of type [InvalidOperationException], with FullyQualifiedErrorId 'DifferentId!' to be thrown, but the exception type was [ArgumentException] and the FullyQualifiedErrorId was 'SomeId!'." + } + + It "Given exception that does not match on message and FullyQualifiedErrorId it returns the correct message" { + $err = { { throw [ArgumentException]"halt!" } | Should-Throw -ExceptionMessage 'fail!' -FullyQualifiedErrorId 'fail!' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected an exception, with message 'fail!', with FullyQualifiedErrorId 'fail!' to be thrown, but the message was 'halt!' and the FullyQualifiedErrorId was 'halt!'." + } + + It "Given exception that does not match on type, message and FullyQualifiedErrorId it returns the correct message" { + $err = { { throw [ArgumentException]"halt!" } | Should-Throw -ExceptionType ([System.InvalidOperationException]) -ExceptionMessage 'fail!' -FullyQualifiedErrorId 'fail!' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected an exception, of type [InvalidOperationException], with message 'fail!' and with FullyQualifiedErrorId 'fail!' to be thrown, but the exception type was [ArgumentException], the message was 'halt!' and the FullyQualifiedErrorId was 'halt!'." + } + } + + Context "Unwrapping exception from different sources" { + It 'Exception is thrown by throw keyword' { + { throw "fail!" } | Should-Throw + } + + It 'Exception is thrown by static .net method' { + { [io.directory]::delete("non-existing") } | Should-Throw + } + + It 'Exception is thrown by failed constructor' { + { New-Object DateTime "incorrect parameter" } | Should-Throw + } + + # division by zero circumvents try catch in pwsh v2 + # so we divide by $null to trigger the same exception + It 'Exception is thrown by division by zero' { + { 1 / $null } | Should-Throw + } + + It 'Terminating error is thrown by cmdlet failing to bind paramaters' { + { Get-Item "non-existing" } | Should-Throw + } + + It 'Terminating error is thrown by cmdlet with -ErrorAction Stop' { + { Get-Item "non-existing" -ErrorAction 'stop' } | Should-Throw + } + + It 'Non-terminating error is thrown by cmdlet and converted to terminating error by the assertion' { + { Get-Item "non-existing" } | Should-Throw + } + } + + It "Given scriptblock that throws it returns ErrorRecord to the output" { + $err = { throw [InvalidOperationException]"error" } | Should-Throw + $err | Verify-Type ([Management.Automation.ErrorRecord]) + $err.Exception | Verify-Type ([System.InvalidOperationException]) + $err.Exception.Message | Verify-Equal "error" + } +} + +Describe "General try catch behavior" { + It 'Gets error record when exception is thrown by throw keyword' { + try { + & { throw "fail!" } + } + catch { + $err = $_ + } + + $err | Verify-NotNull + $err | Verify-Type ([Management.Automation.ErrorRecord]) + } + + It 'Gets error record when exception is thrown from .net' { + try { + & { [io.directory]::delete("non-existing"); } + } + catch { + $err = $_ + } + + $err | Verify-NotNull + $err | Verify-Type ([Management.Automation.ErrorRecord]) + } + + It 'Gets error record when non-terminating error is translated to terminating error' { + try { + & { Get-Item "non-existing" -ErrorAction 'stop' } + } + catch { + $err = $_ + } + + $err | Verify-NotNull + $err | Verify-Type ([Management.Automation.ErrorRecord]) + } + + + It 'Gets error record when non-terminating error is translated to terminating error' { + try { + $ErrorActionPreference = 'stop' + & { Get-Item "non-existing" } + } + catch { + $err = $_ + } + + $err | Verify-NotNull + $err | Verify-Type ([Management.Automation.ErrorRecord]) + } +} + +InPesterModuleScope { + Describe "Get-ErrorObject" { + It 'Unwraps error from invoke with context' { + $ErrorActionPreference = 'stop' + try { + $sb = { + Get-Item "/non-existing" + } + + $eap = [PSVariable]::new("erroractionpreference", 'Stop') + $null = $sb.InvokeWithContext($null, $eap, $null) 2>&1 + } + catch { + $e = $_ + } + + $err = Get-ErrorObject $e + $err.ExceptionMessage | Verify-Like "Cannot find path*because it does not exist." + $err.ExceptionType | Verify-Equal ([Management.Automation.ItemNotFoundException]) + $err.FullyQualifiedErrorId | Verify-Equal 'PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand' + } + } +} diff --git a/tst/functions/assert/General/Should-Be.Tests.ps1 b/tst/functions/assert/General/Should-Be.Tests.ps1 new file mode 100644 index 000000000..7213dc8c0 --- /dev/null +++ b/tst/functions/assert/General/Should-Be.Tests.ps1 @@ -0,0 +1,85 @@ +Set-StrictMode -Version Latest + +Describe "Should-Be" { + Context "Comparing strings" { + It "Passes when two strings are equal" { + "abc" | Should-Be "abc" + } + + It "Fails when two strings are different" { + { "abc" | Should-Be "bde" } | Verify-AssertionFailed + } + } + + Context "Comparing integers" { + It "Passes when two numbers are equal" { + 1 | Should-Be 1 + } + + It "Fails when two numbers are different" { + { 1 | Should-Be 9 } | Verify-AssertionFailed + } + } + + Context "Comparing doubles" { + It "Passes when two numbers are equal" { + .1 | Should-Be .1 + } + + It "Fails when two numbers are different" { + { .1 | Should-Be .9 } | Verify-AssertionFailed + } + } + + Context "Comparing decimals" { + It "Passes when two numbers are equal" { + .1D | Should-Be .1D + } + + It "Fails when two numbers are different" { + { .1D | Should-Be .9D } | Verify-AssertionFailed + } + } + + Context "Comparing objects" { + It "Passes when two objects are the same" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object | Should-Be $object + } + + It "Fails when two objects are different" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object1 = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + { $object | Should-Be $object1 } | Verify-AssertionFailed + } + } + + It "Fails for array input even if the last item is the same as expected" { + { 1, 2, 3 | Should-Be 3 } | Verify-AssertionFailed + } + + Context "Validate messages" { + It "Given two values that are not the same '' and '' it returns expected message ''" -TestCases @( + @{ Expected = "a" ; Actual = 10 ; Message = "Expected [string] 'a', but got [int] 10." }, + @{ Expected = "a" ; Actual = 10.1 ; Message = "Expected [string] 'a', but got [double] 10.1." }, + @{ Expected = "a" ; Actual = 10.1D ; Message = "Expected [string] 'a', but got [decimal] 10.1." } + ) { + $err = { Should-Be -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = 1 + $expected | Should-Be 1 | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-Be 1 2 } | Verify-AssertionFailed + } + + It "Given collection to Expected it throws" { + $err = { "dummy" | Should-Be @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } +} diff --git a/tst/functions/assert/General/Should-BeGreaterThan.Tests.ps1 b/tst/functions/assert/General/Should-BeGreaterThan.Tests.ps1 new file mode 100644 index 000000000..f260644fe --- /dev/null +++ b/tst/functions/assert/General/Should-BeGreaterThan.Tests.ps1 @@ -0,0 +1,103 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeGreaterThan" { + Context "Comparing strings" { + It "Passes when actual is greater than expected" { + "z" | Should-BeGreaterThan "a" + } + + It "Fails when actual is equal to expected" { + { "a" | Should-BeGreaterThan "a" } | Verify-AssertionFailed + } + + It "Fails when actual is lower than expected" { + { "a" | Should-BeGreaterThan "z" } | Verify-AssertionFailed + } + } + + Context "Comparing integers" { + It "Passes when expected is greater than actual" { + 2 | Should-BeGreaterThan 1 + } + + It "Fails when actual is equal to expected" { + { 1 | Should-BeGreaterThan 1 } | Verify-AssertionFailed + } + + It "Fails when actual is lower than expected" { + { 1 | Should-BeGreaterThan 9 } | Verify-AssertionFailed + } + } + + Context "Comparing doubles" { + It "Passes when expected is greater than actual" { + .2 | Should-BeGreaterThan .1 + } + + It "Fails when actual is equal to expected" { + { .1 | Should-BeGreaterThan .1 } | Verify-AssertionFailed + } + + It "Fails when actual is lower than expected" { + { .1 | Should-BeGreaterThan .9 } | Verify-AssertionFailed + } + } + + Context "Comparing decimals" { + It "Passes when expected is greater than actual" { + 2D | Should-BeGreaterThan 1D + } + + It "Fails when actual is equal to expected" { + { 1D | Should-BeGreaterThan 1D } | Verify-AssertionFailed + } + + It "Fails when actual is lower than expected" { + { 1D | Should-BeGreaterThan 9D } | Verify-AssertionFailed + } + } + + Context "Comparing objects" { + It "Fails when two objects are the same" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + { $object | Should-BeGreaterThan $object } | Verify-AssertionFailed + } + + It "Fails when two objects are not comparable" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object1 = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $err = { $object | Should-BeGreaterThan $object1 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.ExtendedTypeSystemException]) + } + } + + It "Fails for array input even if the last item is greater than then expected value" { + $err = { 1, 2, 3, 4 | Should-BeGreaterThan 3 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.RuntimeException]) + } + + Context "Validate messages" { + It "Given two values '' and '' it returns expected message ''" -TestCases @( + @{ Expected = "z" ; Actual = "a" ; Message = "Expected the actual value to be greater than [string] 'z', but it was not. Actual: [string] 'a'" }, + @{ Expected = 10.1 ; Actual = 1.1 ; Message = "Expected the actual value to be greater than [double] 10.1, but it was not. Actual: [double] 1.1" }, + @{ Expected = 10.1D ; Actual = 1.1D ; Message = "Expected the actual value to be greater than [decimal] 10.1, but it was not. Actual: [decimal] 1.1" } + ) { + $err = { Should-BeGreaterThan -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = 1 + $expected | Should-BeGreaterThan 0 | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeGreaterThan 2 1 } | Verify-AssertionFailed + } + + It "Given collection to Expected it throws" { + $err = { "dummy" | Should-BeGreaterThan @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } +} diff --git a/tst/functions/assert/General/Should-BeGreaterThanOrEqual.Tests.ps1 b/tst/functions/assert/General/Should-BeGreaterThanOrEqual.Tests.ps1 new file mode 100644 index 000000000..6868c0dbd --- /dev/null +++ b/tst/functions/assert/General/Should-BeGreaterThanOrEqual.Tests.ps1 @@ -0,0 +1,103 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeGreaterThanOrEqual" { + Context "Comparing strings" { + It "Passes when actual is greater than expected" { + "z" | Should-BeGreaterThanOrEqual "a" + } + + It "Passes when actual is equal to expected" { + "a" | Should-BeGreaterThanOrEqual "a" + } + + It "Fails when actual is lower than expected" { + { "a" | Should-BeGreaterThanOrEqual "z" } | Verify-AssertionFailed + } + } + + Context "Comparing integers" { + It "Passes when expected is greater than actual" { + 2 | Should-BeGreaterThanOrEqual 1 + } + + It "Passes when actual is equal to expected" { + 1 | Should-BeGreaterThanOrEqual 1 + } + + It "Fails when actual is lower than expected" { + { 1 | Should-BeGreaterThanOrEqual 9 } | Verify-AssertionFailed + } + } + + Context "Comparing doubles" { + It "Passes when expected is greater than actual" { + .2 | Should-BeGreaterThanOrEqual .1 + } + + It "Passes when actual is equal to expected" { + .1 | Should-BeGreaterThanOrEqual .1 + } + + It "Fails when actual is lower than expected" { + { .1 | Should-BeGreaterThanOrEqual .9 } | Verify-AssertionFailed + } + } + + Context "Comparing decimals" { + It "Passes when expected is greater than actual" { + 2D | Should-BeGreaterThanOrEqual 1D + } + + It "Passes when actual is equal to expected" { + 1D | Should-BeGreaterThanOrEqual 1D + } + + It "Fails when actual is lower than expected" { + { 1D | Should-BeGreaterThanOrEqual 9D } | Verify-AssertionFailed + } + } + + Context "Comparing objects" { + It "Passes when two objects are the same" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object | Should-BeGreaterThanOrEqual $object + } + + It "Fails when two objects are not comparable" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object1 = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $err = { $object | Should-BeGreaterThanOrEqual $object1 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.ExtendedTypeSystemException]) + } + } + + It "Fails for array input even if the last item is greater than then expected value" { + $err = { 1, 2, 3, 4 | Should-BeGreaterThanOrEqual 3 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.RuntimeException]) + } + + Context "Validate messages" { + It "Given two values '' and '' it returns expected message ''" -TestCases @( + @{ Expected = "z" ; Actual = "a" ; Message = "Expected the actual value to be greater than or equal to [string] 'z', but it was not. Actual: [string] 'a'" }, + @{ Expected = 10.1 ; Actual = 1.1 ; Message = "Expected the actual value to be greater than or equal to [double] 10.1, but it was not. Actual: [double] 1.1" }, + @{ Expected = 10.1D ; Actual = 1.1D ; Message = "Expected the actual value to be greater than or equal to [decimal] 10.1, but it was not. Actual: [decimal] 1.1" } + ) { + $err = { Should-BeGreaterThanOrEqual -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = 1 + $expected | Should-BeGreaterThanOrEqual 0 | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeGreaterThanOrEqual 2 1 } | Verify-AssertionFailed + } + + It "Given collection to Expected it throws" { + $err = { "dummy" | Should-BeGreaterThanOrEqual @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } +} diff --git a/tst/functions/assert/General/Should-BeLessThan.Tests.ps1 b/tst/functions/assert/General/Should-BeLessThan.Tests.ps1 new file mode 100644 index 000000000..382ba0d6a --- /dev/null +++ b/tst/functions/assert/General/Should-BeLessThan.Tests.ps1 @@ -0,0 +1,103 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeLessThan" { + Context "Comparing strings" { + It "Passes when actual is less than expected" { + "a" | Should-BeLessThan "z" + } + + It "Fails when actual is equal to expected" { + { "z" | Should-BeLessThan "z" } | Verify-AssertionFailed + } + + It "Fails when actual is greater than expected" { + { "z" | Should-BeLessThan "a" } | Verify-AssertionFailed + } + } + + Context "Comparing integers" { + It "Passes when expected is less than actual" { + 1 | Should-BeLessThan 2 + } + + It "Fails when actual is equal to expected" { + { 1 | Should-BeLessThan 1 } | Verify-AssertionFailed + } + + It "Fails when actual is greater than expected" { + { 9 | Should-BeLessThan 1 } | Verify-AssertionFailed + } + } + + Context "Comparing doubles" { + It "Passes when expected is less than actual" { + .1 | Should-BeLessThan .2 + } + + It "Fails when actual is equal to expected" { + { .1 | Should-BeLessThan .1 } | Verify-AssertionFailed + } + + It "Fails when actual is greater than expected" { + { .9 | Should-BeLessThan .1 } | Verify-AssertionFailed + } + } + + Context "Comparing decimals" { + It "Passes when expected is less than actual" { + 1D | Should-BeLessThan 2D + } + + It "Fails when actual is equal to expected" { + { 1D | Should-BeLessThan 1D } | Verify-AssertionFailed + } + + It "Fails when actual is greater than expected" { + { 9D | Should-BeLessThan 1D } | Verify-AssertionFailed + } + } + + Context "Comparing objects" { + It "Fails when two objects are the same" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + { $object | Should-BeLessThan $object } | Verify-AssertionFailed + } + + It "Fails when two objects are not comparable" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object1 = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $err = { $object | Should-BeLessThan $object1 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.ExtendedTypeSystemException]) + } + } + + It "Fails for array input even if the last item is less than the expected value" { + $err = { 4, 3, 2, 1 | Should-BeLessThan 3 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.RuntimeException]) + } + + Context "Validate messages" { + It "Given two values '' and '' it returns expected message ''" -TestCases @( + @{ Expected = "a" ; Actual = "z" ; Message = "Expected the actual value to be less than [string] 'a', but it was not. Actual: [string] 'z'" }, + @{ Expected = 1.1 ; Actual = 10.1 ; Message = "Expected the actual value to be less than [double] 1.1, but it was not. Actual: [double] 10.1" }, + @{ Expected = 1.1D ; Actual = 10.1D ; Message = "Expected the actual value to be less than [decimal] 1.1, but it was not. Actual: [decimal] 10.1" } + ) { + $err = { Should-BeLessThan -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = 0 + $expected | Should-BeLessThan 1 | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeLessThan 1 2 } | Verify-AssertionFailed + } + + It "Given collection to Expected it throws" { + $err = { "dummy" | Should-BeLessThan @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } +} diff --git a/tst/functions/assert/General/Should-BeLessThanOrEqual.Tests.ps1 b/tst/functions/assert/General/Should-BeLessThanOrEqual.Tests.ps1 new file mode 100644 index 000000000..754eb2ced --- /dev/null +++ b/tst/functions/assert/General/Should-BeLessThanOrEqual.Tests.ps1 @@ -0,0 +1,103 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeLessThanOrEqual" { + Context "Comparing strings" { + It "Passes when actual is less than expected" { + "a" | Should-BeLessThanOrEqual "z" + } + + It "Passes when actual is equal to expected" { + "a" | Should-BeLessThanOrEqual "a" + } + + It "Fails when actual is greater than expected" { + { "z" | Should-BeLessThanOrEqual "a" } | Verify-AssertionFailed + } + } + + Context "Comparing integers" { + It "Passes when expected is less than actual" { + 1 | Should-BeLessThanOrEqual 2 + } + + It "Passes when actual is equal to expected" { + 1 | Should-BeLessThanOrEqual 1 + } + + It "Fails when actual is greater than expected" { + { 9 | Should-BeLessThanOrEqual 1 } | Verify-AssertionFailed + } + } + + Context "Comparing doubles" { + It "Passes when expected is less than actual" { + .1 | Should-BeLessThanOrEqual .2 + } + + It "Passes when actual is equal to expected" { + .1 | Should-BeLessThanOrEqual .1 + } + + It "Fails when actual is greater than expected" { + { .9 | Should-BeLessThanOrEqual .1 } | Verify-AssertionFailed + } + } + + Context "Comparing decimals" { + It "Passes when expected is less than actual" { + 1D | Should-BeLessThanOrEqual 2D + } + + It "Passes when actual is equal to expected" { + 1D | Should-BeLessThanOrEqual 1D + } + + It "Fails when actual is greater than expected" { + { 9D | Should-BeLessThanOrEqual 1D } | Verify-AssertionFailed + } + } + + Context "Comparing objects" { + It "Passes when two objects are the same" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object | Should-BeLessThanOrEqual $object + } + + It "Fails when two objects are not comparable" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object1 = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $err = { $object | Should-BeLessThanOrEqual $object1 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.ExtendedTypeSystemException]) + } + } + + It "Fails for array input even if the last item is less than then expected value" { + $err = { 4, 3, 2, 1 | Should-BeLessThanOrEqual 3 } | Verify-Throw + $err.Exception | Verify-Type ([System.Management.Automation.RuntimeException]) + } + + Context "Validate messages" { + It "Given two values '' and '' it returns expected message ''" -TestCases @( + @{ Expected = "a" ; Actual = "z" ; Message = "Expected the actual value to be less than or equal to [string] 'a', but it was not. Actual: [string] 'z'" }, + @{ Expected = 1.1 ; Actual = 10.1 ; Message = "Expected the actual value to be less than or equal to [double] 1.1, but it was not. Actual: [double] 10.1" }, + @{ Expected = 1.1D ; Actual = 10.1D ; Message = "Expected the actual value to be less than or equal to [decimal] 1.1, but it was not. Actual: [decimal] 10.1" } + ) { + $err = { Should-BeLessThanOrEqual -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = 0 + $expected | Should-BeLessThanOrEqual 1 | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-BeLessThanOrEqual 1 2 } | Verify-AssertionFailed + } + + It "Given collection to Expected it throws" { + $err = { "dummy" | Should-BeLessThanOrEqual @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } +} diff --git a/tst/functions/assert/General/Should-BeNull.Tests.ps1 b/tst/functions/assert/General/Should-BeNull.Tests.ps1 new file mode 100644 index 000000000..7398ae333 --- /dev/null +++ b/tst/functions/assert/General/Should-BeNull.Tests.ps1 @@ -0,0 +1,27 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeNull" { + It "Given `$null it passes" { + $null | Should-BeNull + } + + It "Given an objects it fails" { + { 1 | Should-BeNull } | Verify-AssertionFailed + } + + It "Given empty array it fails" { + { @() | Should-BeNull } | Verify-AssertionFailed + } + + It "Returns the given value" { + $null | Should-BeNull | Verify-Null + } + + It "Can be called with positional parameters (1)" { + { Should-BeNull 1 } | Verify-AssertionFailed + } + + It "Can be called with positional parameters (@())" { + { Should-BeNull @() } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/General/Should-BeSame.Tests.ps1 b/tst/functions/assert/General/Should-BeSame.Tests.ps1 new file mode 100644 index 000000000..a49a342ab --- /dev/null +++ b/tst/functions/assert/General/Should-BeSame.Tests.ps1 @@ -0,0 +1,50 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeSame" { + It "Passes when two objects are the same instance" { + $object = New-Object Diagnostics.Process + $object | Should-BeSame $object + } + + It "Fails when two objects are different instance" { + $object = New-Object Diagnostics.Process + $object1 = New-Object Diagnostics.Process + { $object | Should-BeSame $object1 } | Verify-AssertionFailed + } + + It "Fails for array input even if the last item is the same as expected" { + $object = New-Object Diagnostics.Process + { 1, 2, $object | Should-BeSame $object } | Verify-AssertionFailed + } + + It "Given two values that are not the same instance '' and '' it returns expected message ''" -TestCases @( + @{ Expected = New-Object -TypeName PSObject ; Actual = New-Object -TypeName PSObject ; Message = "Expected [PSObject] PSObject{}, to be the same instance but it was not. Actual: [PSObject] PSObject{}" } + ) { + $err = { Should-BeSame -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + + It "Returns the value on output" { + $expected = New-Object Diagnostics.Process + $expected | Should-BeSame $expected | Verify-Equal $expected + } + + Context "Throws when `$expected is a value type or string to warn user about unexpected behavior" { + It "throws for value " -TestCases @( + @{ Value = 1 } + @{ Value = 1.0D } + @{ Value = 1.0 } + @{ Value = 'c' } + @{ Value = "abc" } + ) { + $err = { "dummy" | Should-BeSame -Expected $Value } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + $err.Exception.Message | Verify-Equal "Should-BeSame compares objects by reference. You provided a value type or a string, those are not reference types and you most likely don't need to compare them by reference, see https://github.com/nohwnd/Assert/issues/6.`n`nAre you trying to compare two values to see if they are equal? Use Should-BeEqual instead." + } + } + + It "Can be called with positional parameters" { + $object = New-Object Diagnostics.Process + { Should-BeSame $object "abc" } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/General/Should-HaveType.Tests.ps1 b/tst/functions/assert/General/Should-HaveType.Tests.ps1 new file mode 100644 index 000000000..ba064cb3d --- /dev/null +++ b/tst/functions/assert/General/Should-HaveType.Tests.ps1 @@ -0,0 +1,19 @@ +Set-StrictMode -Version Latest + +Describe "Should-HaveType" { + It "Given value of expected type it passes" { + 1 | Should-HaveType ([int]) + } + + It "Given an object of different type it fails" { + { 1 | Should-HaveType ([string]) } | Verify-AssertionFailed + } + + It "Returns the given value" { + 'b' | Should-HaveType ([string]) | Verify-Equal 'b' + } + + It "Can be called with positional parameters" { + { Should-HaveType ([string]) 1 } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/General/Should-NotBe.Tests.ps1 b/tst/functions/assert/General/Should-NotBe.Tests.ps1 new file mode 100644 index 000000000..c03f28826 --- /dev/null +++ b/tst/functions/assert/General/Should-NotBe.Tests.ps1 @@ -0,0 +1,85 @@ +Set-StrictMode -Version Latest + +Describe "Should-NotBe" { + Context "Comparing strings" { + It "Fails when two strings are equal" { + { "abc" | Should-NotBe "abc" } | Verify-AssertionFailed + } + + It "Passes when two strings are different" { + "abc" | Should-NotBe "bde" + } + } + + Context "Comparing integers" { + It "Fails when two numbers are equal" { + { 1 | Should-NotBe 1 } | Verify-AssertionFailed + } + + It "Passes when two numbers are different" { + 1 | Should-NotBe 9 + } + } + + Context "Comparing doubles" { + It "Fails when two numbers are equal" { + { .1 | Should-NotBe .1 } | Verify-AssertionFailed + } + + It "Passes when two numbers are different" { + .1 | Should-NotBe .9 + } + } + + Context "Comparing decimals" { + It "Fails when two numbers are equal" { + { .1D | Should-NotBe .1D } | Verify-AssertionFailed + } + + It "Passes when two numbers are different" { + .1D | Should-NotBe .9D + } + } + + Context "Comparing objects" { + It "Fails when two objects are the same" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + { $object | Should-NotBe $object } | Verify-AssertionFailed + } + + It "Passes when two objects are different" { + $object = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object1 = New-Object -TypeName PsObject -Property @{ Name = "Jakub" } + $object | Should-NotBe $object1 + } + } + + It "Passes for array input even if the last item is the same as expected" { + 1, 2, 3 | Should-NotBe 3 + } + + Context "Validate messages" { + It "Given two values that are the same '' it returns expected message ''" -TestCases @( + @{ Value = 1; Message = "Expected [int] 1, to be different than the actual value, but they were equal." } + ) { + param($Value, $Message) + $err = { Should-NotBe -Actual $Value -Expected $Value } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } + + It "Returns the value on output" { + $expected = 1 + $expected | Should-NotBe 9 | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { Should-NotBe 1 1 } | Verify-AssertionFailed + } + + It "Given collection to Expected it throws" { + $err = { "dummy" | Should-NotBe @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } +} + diff --git a/tst/functions/assert/General/Should-NotBeNull.Tests.ps1 b/tst/functions/assert/General/Should-NotBeNull.Tests.ps1 new file mode 100644 index 000000000..203dd44b1 --- /dev/null +++ b/tst/functions/assert/General/Should-NotBeNull.Tests.ps1 @@ -0,0 +1,21 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Should-NotBeNull" { + It "Given a value it passes" { + 1 | Should-NotBeNull + } + + It "Given `$null it fails" { + { $null | Should-NotBeNull } | Verify-AssertionFailed + } + + It "Returns the given value" { + 1 | Should-NotBeNull | Verify-NotNull + } + + It "Can be called with positional parameters" { + { Should-NotBeNull $null } | Verify-AssertionFailed + } + } +} diff --git a/tst/functions/assert/General/Should-NotBeSame.Tests.ps1 b/tst/functions/assert/General/Should-NotBeSame.Tests.ps1 new file mode 100644 index 000000000..4310c614d --- /dev/null +++ b/tst/functions/assert/General/Should-NotBeSame.Tests.ps1 @@ -0,0 +1,39 @@ +Set-StrictMode -Version Latest + +Describe "Should-NotBeSame" { + It "Fails when two objects are the same instance" { + $object = New-Object Diagnostics.Process + { $object | Should-NotBeSame $object } | Verify-AssertionFailed + } + + It "Passes when two objects are different instance" { + $object = New-Object Diagnostics.Process + $object1 = New-Object Diagnostics.Process + $object | Should-NotBeSame $object1 + } + + It "Passes for array input even if the last item is the same as expected" { + $object = New-Object Diagnostics.Process + 1, 2, $object | Should-NotBeSame $object + } + + It "Given two values that are the same instance it returns expected message ''" -TestCases @( + @{ Value = "a"; Message = "Expected [string] 'a', to not be the same instance, but they were the same instance." } + ) { + $err = { Should-NotBeSame -Actual $Value -Expected $Value } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + + It "Returns the value on output" { + $expected = 1 + $expected | Should-NotBeSame 7 | Verify-Equal $expected + } + + It "Can be called with positional parameters" { + { + $obj = New-Object -TypeName PSObject + Should-NotBeSame $obj $obj + } | Verify-AssertionFailed + } +} + diff --git a/tst/functions/assert/General/Should-NotHaveType.Tests.ps1 b/tst/functions/assert/General/Should-NotHaveType.Tests.ps1 new file mode 100644 index 000000000..00b598dd0 --- /dev/null +++ b/tst/functions/assert/General/Should-NotHaveType.Tests.ps1 @@ -0,0 +1,19 @@ +Set-StrictMode -Version Latest + +Describe "Should-NotHaveType" { + It "Given value of expected type it fails" { + { 1 | Should-NotHaveType ([int]) } | Verify-AssertionFailed + } + + It "Given an object of different type it passes" { + 1 | Should-NotHaveType ([string]) + } + + It "Returns the given value" { + 'b' | Should-NotHaveType ([int]) | Verify-Equal 'b' + } + + It "Can be called with positional parameters" { + { Should-NotHaveType ([int]) 1 } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/Get-CustomFailureMessage.Tests.ps1 b/tst/functions/assert/Get-CustomFailureMessage.Tests.ps1 new file mode 100644 index 000000000..f89b75178 --- /dev/null +++ b/tst/functions/assert/Get-CustomFailureMessage.Tests.ps1 @@ -0,0 +1,29 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Get-CustomFailureMessage" { + It "returns correct custom message when no tokens are provided" { + $expected = "Static failure message." + $customMessage = "Static failure message." + Get-CustomFailureMessage -CustomMessage $customMessage -Expected 1 -Actual 2 | Verify-Equal $expected + } + + It "returns correct custom message when positional tokens are provided" { + $expected = "We expected string to be 1, because that is the default value, but got 2." + $customMessage = "We expected string to be {0}, because that is the default value, but got {1}." + Get-CustomFailureMessage -CustomMessage $customMessage -Expected 1 -Actual 2 | Verify-Equal $expected + } + + It "returns correct custom message when named tokens are provided" { + $expected = "We expected string to be 1, because that is the default value, but got 2." + $customMessage = "We expected string to be , because that is the default value, but got ." + Get-CustomFailureMessage -CustomMessage $customMessage -Expected 1 -Actual 2 | Verify-Equal $expected + } + + It "returns correct custom message when shortened named tokens are provided" { + $expected = "We expected string to be 1, because that is the default value, but got 2." + $customMessage = "We expected string to be , because that is the default value, but got ." + Get-CustomFailureMessage -CustomMessage $customMessage -Expected 1 -Actual 2 | Verify-Equal $expected + } + } +} diff --git a/tst/functions/assert/String/Should-BeEmptyString.Tests.ps1 b/tst/functions/assert/String/Should-BeEmptyString.Tests.ps1 new file mode 100644 index 000000000..7005c20df --- /dev/null +++ b/tst/functions/assert/String/Should-BeEmptyString.Tests.ps1 @@ -0,0 +1,61 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeEmptyString" { + It "Does not throw when string is empty" { + Should-BeEmptyString -Actual "" + } + + It "EDGE CASE: Does not throw when string is single item collection of empty string" { + @("") | Should-BeEmptyString + } + + It "Throws when string is not empty" -ForEach @( + @{ Actual = "a" } + @{ Actual = " " } + ) { + { Should-BeEmptyString -Actual $Actual } | Verify-AssertionFailed + } + + It "Throws when `$Actual is not a string" -ForEach @( + @{ Actual = $true } + @{ Actual = 1 } + @{ Actual = @() } + @{ Actual = $null } + ) { + { Should-BeEmptyString -Actual $Actual } | Verify-AssertionFailed + } + + It "Throws when type serializes to empty string" { + Add-type -TypeDefinition " + public class TypeThatSerializesToEmptyString { public override string ToString() { return string.Empty; } } + " + { Should-BeEmptyString -Actual ([TypeThatSerializesToEmptyString]::new()) } | Verify-AssertionFailed + } + + It "Allows actual to be passed from pipeline" { + "" | Should-BeEmptyString + } + + It "Allows actual to be passed by position" { + Should-BeEmptyString "" + } + + It "Fails when empty collection is passed in by pipeline" { + { @() | Should-BeEmptyString } | Verify-AssertionFailed + } + + It "Fails when `$null collection is passed in by pipeline" { + { $null | Should-BeEmptyString } | Verify-AssertionFailed + } + + It "Fails with the expected message" -ForEach @( + @{ Actual = "a"; Because = $null; ExpectedMessage = "Expected a [string] that is empty, but got [string]: 'a'`n`n" } + @{ Actual = "a"; Because = 'reason'; ExpectedMessage = "Expected a [string] that is empty, because reason, but got [string]: 'a'`n`n" } + @{ Actual = 3; Because = $null; ExpectedMessage = "Expected a [string] that is empty, but got [int]: 3`n`n" } + ) { + $actual = $Actual + $expectedMessage = $ExpectedMessage + $err = { Should-BeEmptyString -Actual $actual -Because $Because } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $ExpectedMessage + } +} diff --git a/tst/functions/assert/String/Should-BeLikeString.Tests.ps1 b/tst/functions/assert/String/Should-BeLikeString.Tests.ps1 new file mode 100644 index 000000000..6707b096b --- /dev/null +++ b/tst/functions/assert/String/Should-BeLikeString.Tests.ps1 @@ -0,0 +1,139 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeLikeString" { + Context "Case insensitive matching" { + It "Passes give strings that have the same value" { + Should-BeLikeString -Expected "abc" -Actual "abc" + } + + It "Passes given strings with different case and same values. comparing '':''" -TestCases @( + @{ Actual = "ABc"; Expected = "abc" }, + @{ Actual = "aBc"; Expected = "abc" }, + @{ Actual = "ABC"; Expected = "abc" } + ) { + param ($Actual, $Expected) + Should-BeLikeString -Actual $Actual -Expected $Expected + } + + It "Fails given strings with different values" { + { Should-BeLikeString -Expected "abc" -Actual "def" } | Verify-AssertionFailed + } + + It "Fails given strings with different case and different values. comparing '':''" -TestCases @( + @{ Actual = "ABc"; Expected = "def" }, + @{ Actual = "aBc"; Expected = "def" }, + @{ Actual = "ABC"; Expected = "def" } + ) { + param ($Actual, $Expected) + { Should-BeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + } + + It "Fails given strings from which one is sorrounded by whitespace. comparing '':''" -TestCases @( + @{ Actual = "abc "; Expected = "abc" }, + @{ Actual = "abc "; Expected = "abc" }, + @{ Actual = "ab c"; Expected = "abc" } + ) { + param ($Actual, $Expected) + { Should-BeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + } + + It "Passes given strings with different case that start with a given pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "abc*" }, + @{ Actual = "aBcdef"; Expected = "abc*" }, + @{ Actual = "ABCDEF"; Expected = "abc*" } + ) { + param ($Actual, $Expected) + Should-BeLikeString -Actual $Actual -Expected $Expected + } + + It "Fails given strings with different case that start with a different pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "ghi*" }, + @{ Actual = "aBcdef"; Expected = "ghi*" }, + @{ Actual = "ABCDEF"; Expected = "ghi*" } + ) { + param ($Actual, $Expected) + { Should-BeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + } + + It "Passes given strings with different case that contain a given pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "*cd*" }, + @{ Actual = "aBcdef"; Expected = "*cd*" }, + @{ Actual = "ABCDEF"; Expected = "*CD*" } + ) { + param ($Actual, $Expected) + Should-BeLikeString -Actual $Actual -Expected $Expected + } + + It "Fails given strings with different case that contain a different pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "*gh*" }, + @{ Actual = "aBcdef"; Expected = "*gh*" }, + @{ Actual = "ABCDEF"; Expected = "*GH*" } + ) { + param ($Actual, $Expected) + { Should-BeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + } + } + + Context "Case sensitive matching" { + It "Fails given strings with different case but same values. comparing '':''" -TestCases @( + @{ Actual = "ABc"; Expected = "abc" }, + @{ Actual = "aBc"; Expected = "abc" }, + @{ Actual = "ABC"; Expected = "abc" } + ) { + param ($Actual, $Expected) + { Should-BeLikeString -Actual $Actual -Expected $Expected -CaseSensitive } | Verify-AssertionFailed + } + } + + Context "Case sensitive matching" { + It "Fails given strings with different case that contain the given pattern. comparing '':''" -TestCases @( + @{ Actual = "ABCDEF"; Expected = "*cd*" } + ) { + param ($Actual, $Expected) + { Should-BeLikeString -Actual $Actual -Expected $Expected -CaseSensitive } | Verify-AssertionFailed + } + } + + It "Allows actual to be passed from pipeline" { + "abc" | Should-BeLikeString -Expected "abc" + } + + It "Allows expected to be passed by position" { + Should-BeLikeString "abc" -Actual "abc" + } + + It "Allows actual to be passed by pipeline and expected by position" { + "abc" | Should-BeLikeString "abc" + } + + It "Throws when given a collection to avoid confusing matches of the last item only" { + $err = { "bde", "abc" | Should-BeLikeString -Expected "abc" } | Verify-Throw + $err.Exception.Message | Verify-Equal "Actual is expected to be string, to avoid confusing behavior that -like operator exhibits with collections. To assert on collections use Should-Any, Should-All or some other collection assertion." + } + + It "Can be called with positional parameters" { + { Should-BeLikeString "a" "b" } | Verify-AssertionFailed + } + + Context "Verify messages" { + It "Given two values that are not alike '' and '' it returns the correct message ''" -TestCases @( + @{ Actual = 'a'; Expected = 'b'; Message = "Expected the string 'a' to be like 'b', but it did not." } + @{ Actual = 'ab'; Expected = 'd*'; Message = "Expected the string 'ab' to be like 'd*', but it did not." } + @{ Actual = 'something'; Expected = '*abc*'; Message = "Expected the string 'something' to be like '*abc*', but it did not." } + ) { + param ($Actual, $Expected, $Message) + $err = { Should-BeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + + It "Given two values that are not alike becuase of case '' and '' it returns the correct message ''" -TestCases @( + @{ Actual = 'a'; Expected = 'B'; Message = "Expected the string 'a' to case sensitively be like 'B', but it did not." } + @{ Actual = 'ab'; Expected = 'B*'; Message = "Expected the string 'ab' to case sensitively be like 'B*', but it did not." } + @{ Actual = 'something'; Expected = '*SOME*'; Message = "Expected the string 'something' to case sensitively be like '*SOME*', but it did not." } + ) { + param ($Actual, $Expected, $Message) + $err = { Should-BeLikeString -Actual $Actual -Expected $Expected -CaseSensitive } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } +} diff --git a/tst/functions/assert/String/Should-BeString.Tests.ps1 b/tst/functions/assert/String/Should-BeString.Tests.ps1 new file mode 100644 index 000000000..f90ca04ce --- /dev/null +++ b/tst/functions/assert/String/Should-BeString.Tests.ps1 @@ -0,0 +1,110 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Test-StringEqual" { + Context "Type matching" { + It "Returns false for non-string" { + Test-StringEqual -Expected "1" -Actual 1 | Verify-False + } + } + Context "Case insensitive matching" { + It "strings with the same values are equal" { + Test-StringEqual -Expected "abc" -Actual "abc" | Verify-True + } + + It "strings with different case and same values are equal. comparing '':''" -TestCases @( + @{l = "ABc"; r = "abc" }, + @{l = "aBc"; r = "abc" }, + @{l = "ABC"; r = "abc" } + ) { + Test-StringEqual -Expected $l -Actual $r | Verify-True + } + + It "strings with different values are not equal" { + Test-StringEqual -Expected "abc" -Actual "def" | Verify-False + } + + It "strings with different case and different values are not equal. comparing '':''" -TestCases @( + @{l = "ABc"; r = "def" }, + @{l = "aBc"; r = "def" }, + @{l = "ABC"; r = "def" } + ) { + Test-StringEqual -Expected $l -Actual $r | Verify-False + } + + It "strings from which one is sorrounded by whitespace are not equal. comparing '':''" -TestCases @( + @{l = "abc "; r = "abc" }, + @{l = "abc "; r = "abc" }, + @{l = "ab c"; r = "abc" } + ) { + Test-StringEqual -Expected $l -Actual $r | Verify-False + } + } + + Context "Case sensitive matching" { + It "strings with different case but same values are not equal. comparing '':''" -TestCases @( + @{l = "ABc"; r = "abc" }, + @{l = "aBc"; r = "abc" }, + @{l = "ABC"; r = "abc" } + ) { + Test-StringEqual -Expected $l -Actual $r -CaseSensitive | Verify-False + } + } + + Context "Case insensitive matching with ingoring whitespace" { + It "strings sorrounded or containing whitespace are equal. comparing '':''" -TestCases @( + @{l = "abc "; r = "abc" }, + @{l = "abc "; r = "abc" }, + @{l = "ab c"; r = "abc" }, + @{l = "ab c"; r = "a b c" } + ) { + Test-StringEqual -Expected $l -Actual $r -IgnoreWhiteSpace | Verify-True + } + } + } +} + +Describe "Should-BeString" { + It "Does nothing when string are the same" { + Should-BeString -Expected "abc" -Actual "abc" + } + + It "Throws when strings are different" { + { Should-BeString -Expected "abc" -Actual "bde" } | Verify-AssertionFailed + } + + It "Allows actual to be passed from pipeline" { + "abc" | Should-BeString -Expected "abc" + } + + It "Allows expected to be passed by position" { + Should-BeString "abc" -Actual "abc" + } + + It "Allows actual to be passed by pipeline and expected by position" { + "abc" | Should-BeString "abc" + } + + It "Fails when collection of strings is passed in by pipeline, even if the last string is the same as the expected string" { + { "bde", "abc" | Should-BeString -Expected "abc" } | Verify-AssertionFailed + } + + Context "String specific features" { + It "Can compare strings in CaseSensitive mode" { + { Should-BeString -Expected "ABC" -Actual "abc" -CaseSensitive } | Verify-AssertionFailed + } + + It "Can compare strings without whitespace" { + Should-BeString -Expected " a b c " -Actual "abc" -IgnoreWhitespace + } + } + + It "Can be called with positional parameters" { + { Should-BeString "a" "b" } | Verify-AssertionFailed + } + + It "Throws with default message when test fails" { + $err = { Should-BeString -Expected "abc" -Actual "bde" } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected [string] 'abc', but got [string] 'bde'." + } +} diff --git a/tst/functions/assert/String/Should-NotBeLikeString.Tests.ps1 b/tst/functions/assert/String/Should-NotBeLikeString.Tests.ps1 new file mode 100644 index 000000000..7d0ca265e --- /dev/null +++ b/tst/functions/assert/String/Should-NotBeLikeString.Tests.ps1 @@ -0,0 +1,128 @@ +Set-StrictMode -Version Latest + +Describe "Should-NotBeLikeString" { + Context "Case insensitive matching" { + It "Fails give strings that have the same value" { + { Should-NotBeLikeString -Expected "abc" -Actual "abc" } | Verify-AssertionFailed + } + + It "Fails given strings with different case and same values. comparing '':''" -TestCases @( + @{ Actual = "ABc"; Expected = "abc" }, + @{ Actual = "aBc"; Expected = "abc" }, + @{ Actual = "ABC"; Expected = "abc" } + ) { + { Should-NotBeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + } + + It "Passes given strings with different values" { + Should-NotBeLikeString -Expected "abc" -Actual "def" + } + + It "Passes given strings with different case and different values. comparing '':''" -TestCases @( + @{ Actual = "ABc"; Expected = "def" }, + @{ Actual = "aBc"; Expected = "def" }, + @{ Actual = "ABC"; Expected = "def" } + ) { + Should-NotBeLikeString -Actual $Actual -Expected $Expected + } + + It "Passes given strings from which one is sorrounded by whitespace. comparing '':''" -TestCases @( + @{ Actual = "abc "; Expected = "abc" }, + @{ Actual = "abc "; Expected = "abc" }, + @{ Actual = "ab c"; Expected = "abc" } + ) { + Should-NotBeLikeString -Actual $Actual -Expected $Expected + } + + It "Fails given strings with different case that start with a given pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "abc*" }, + @{ Actual = "aBcdef"; Expected = "abc*" }, + @{ Actual = "ABCDEF"; Expected = "abc*" } + ) { + { Should-NotBeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + } + + It "Passes given strings with different case that start with a different pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "ghi*" }, + @{ Actual = "aBcdef"; Expected = "ghi*" }, + @{ Actual = "ABCDEF"; Expected = "ghi*" } + ) { + Should-NotBeLikeString -Actual $Actual -Expected $Expected + } + + It "Fails given strings with different case that contain a given pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "*cd*" }, + @{ Actual = "aBcdef"; Expected = "*cd*" }, + @{ Actual = "ABCDEF"; Expected = "*CD*" } + ) { + { Should-NotBeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + } + + It "Passes given strings with different case that contain a different pattern. comparing '':''" -TestCases @( + @{ Actual = "ABcdef"; Expected = "*gh*" }, + @{ Actual = "aBcdef"; Expected = "*gh*" }, + @{ Actual = "ABCDEF"; Expected = "*GH*" } + ) { + Should-NotBeLikeString -Actual $Actual -Expected $Expected + } + } + + Context "Case sensitive matching" { + It "Passes given strings with different case but same values. comparing '':''" -TestCases @( + @{ Actual = "ABc"; Expected = "abc" }, + @{ Actual = "aBc"; Expected = "abc" }, + @{ Actual = "ABC"; Expected = "abc" } + ) { + Should-NotBeLikeString -Actual $Actual -Expected $Expected -CaseSensitive + } + } + + Context "Case sensitive matching" { + It "Passes given strings with different case that contain the given pattern. comparing '':''" -TestCases @( + @{ Actual = "ABCDEF"; Expected = "*cd*" } + ) { + Should-NotBeLikeString -Actual $Actual -Expected $Expected -CaseSensitive + } + } + + It "Allows actual to be passed from pipeline" { + "efg" | Should-NotBeLikeString -Expected "abc" + } + + It "Allows expected to be passed by position" { + Should-NotBeLikeString "efg" -Actual "abc" + } + + It "Allows actual to be passed by pipeline and expected by position" { + "efg" | Should-NotBeLikeString "abc" + } + + It "Can be called with positional parameters" { + { Should-NotBeLikeString "a" "a" } | Verify-AssertionFailed + } + + It "Throws when given a collection to avoid confusing matches of the last item only" { + $err = { "bde", "abc" | Should-NotBeLikeString -Expected "abc" } | Verify-Throw + $err.Exception.Message | Verify-Equal "Actual is expected to be string, to avoid confusing behavior that -like operator exhibits with collections. To assert on collections use Should-Any, Should-All or some other collection assertion." + } + + Context "Verify messages" { + It "Given two values that are alike '' and '' it returns the correct message ''" -TestCases @( + @{ Actual = 'a'; Expected = 'A'; Message = "Expected the string 'a' to not match 'A' but it matched it." } + @{ Actual = 'ab'; Expected = 'a*'; Message = "Expected the string 'ab' to not match 'a*' but it matched it." } + @{ Actual = 'something'; Expected = 'SOME*'; Message = "Expected the string 'something' to not match 'SOME*' but it matched it." } + ) { + $err = { Should-NotBeLikeString -Actual $Actual -Expected $Expected } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + + It "Given two values that are alike becuase of case '' and '' it returns the correct message ''" -TestCases @( + @{ Actual = 'a'; Expected = 'a'; Message = "Expected the string 'a' to case sensitively not match 'a' but it matched it." } + @{ Actual = 'AB'; Expected = 'A*'; Message = "Expected the string 'AB' to case sensitively not match 'A*' but it matched it." } + @{ Actual = 'SOMETHING'; Expected = '*SOME*'; Message = "Expected the string 'SOMETHING' to case sensitively not match '*SOME*' but it matched it." } + ) { + $err = { Should-NotBeLikeString -Actual $Actual -Expected $Expected -CaseSensitive } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $Message + } + } +} diff --git a/tst/functions/assert/String/Should-NotBeNullOrEmptyString.Tests.ps1 b/tst/functions/assert/String/Should-NotBeNullOrEmptyString.Tests.ps1 new file mode 100644 index 000000000..7dd692ae8 --- /dev/null +++ b/tst/functions/assert/String/Should-NotBeNullOrEmptyString.Tests.ps1 @@ -0,0 +1,55 @@ +Set-StrictMode -Version Latest + +Describe "Should-NotBeNullOrEmptyString" { + It "Does not throw when string has value" -ForEach @( + @{ Actual = "1" } + @{ Actual = " " } + @{ Actual = "`t" } + @{ Actual = "`n" } + ) { + $Actual | Should-NotBeNullOrEmptyString + } + + It "Throws when string is `$null or empty" -ForEach @( + @{ Actual = "" } + @{ Actual = $null } + ) { + { $Actual | Should-NotBeNullOrEmptyString } | Verify-AssertionFailed + } + + It "Throws when value is not string" -ForEach @( + @{ Actual = 1 } + @{ Actual = @() } + @{ Actual = $true } + ) { + { $Actual | Should-NotBeNullOrEmptyString } | Verify-AssertionFailed + } + + + It "Allows actual to be passed from pipeline" { + "abc" | Should-NotBeNullOrEmptyString + } + + It "Allows actual to be passed by position" { + Should-NotBeNullOrEmptyString "abc" + } + + It "Fails when empty collection is passed in by pipeline" { + { @() | Should-NotBeNullOrEmptyString } | Verify-AssertionFailed + } + + It "Fails when `$null collection is passed in by pipeline" { + { $null | Should-NotBeNullOrEmptyString } | Verify-AssertionFailed + } + + It "Fails with the expected message" -ForEach @( + @{ Actual = ""; Because = $null; ExpectedMessage = "Expected a [string] that is not `$null or empty, but got [string]: `n`n" } + @{ Actual = ""; Because = 'reason'; ExpectedMessage = "Expected a [string] that is not `$null or empty, because reason, but got [string]: `n`n" } + @{ Actual = 3; Because = $null; ExpectedMessage = "Expected a [string] that is not `$null or empty, but got [int]: 3`n`n" } + ) { + $actual = $Actual + $expectedMessage = $ExpectedMessage + $err = { Should-NotBeNullOrEmptyString -Actual $actual -Because $Because } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $ExpectedMessage + } +} diff --git a/tst/functions/assert/String/Should-NotBeNullOrWhiteSpaceString.Tests.ps1 b/tst/functions/assert/String/Should-NotBeNullOrWhiteSpaceString.Tests.ps1 new file mode 100644 index 000000000..0a9dc12e4 --- /dev/null +++ b/tst/functions/assert/String/Should-NotBeNullOrWhiteSpaceString.Tests.ps1 @@ -0,0 +1,54 @@ +Set-StrictMode -Version Latest + +Describe "Should-NotBeNullOrWhiteSpaceString" { + It "Does not throw when string has value" { + "bde" | Should-NotBeNullOrWhiteSpaceString + } + + It "Throws when string is emptyish" -ForEach @( + @{ Actual = "" } + @{ Actual = " " } + @{ Actual = "`t" } + @{ Actual = "`n" } + @{ Actual = "`r" } + ) { + { $Actual | Should-NotBeNullOrWhiteSpaceString } | Verify-AssertionFailed + } + + It "Throws when value is not string" -ForEach @( + @{ Actual = 1 } + @{ Actual = @() } + @{ Actual = $true } + @{ Actual = $null } + ) { + { $Actual | Should-NotBeNullOrWhiteSpaceString } | Verify-AssertionFailed + } + + + It "Allows actual to be passed from pipeline" { + "abc" | Should-NotBeNullOrWhiteSpaceString + } + + It "Allows actual to be passed by position" { + Should-NotBeNullOrWhiteSpaceString "abc" + } + + It "Fails when empty collection is passed in by pipeline" { + { @() | Should-NotBeNullOrWhiteSpaceString } | Verify-AssertionFailed + } + + It "Fails when `$null collection is passed in by pipeline" { + { $null | Should-NotBeNullOrWhiteSpaceString } | Verify-AssertionFailed + } + + It "Fails with the expected message" -ForEach @( + @{ Actual = ""; Because = $null; ExpectedMessage = "Expected a [string] that is not `$null, empty or whitespace, but got [string]: `n`n" } + @{ Actual = ""; Because = 'reason'; ExpectedMessage = "Expected a [string] that is not `$null, empty or whitespace, because reason, but got [string]: `n`n" } + @{ Actual = 3; Because = $null; ExpectedMessage = "Expected a [string] that is not `$null, empty or whitespace, but got [int]: 3`n`n" } + ) { + $actual = $Actual + $expectedMessage = $ExpectedMessage + $err = { Should-NotBeNullOrWhiteSpaceString -Actual $actual -Because $Because } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal $ExpectedMessage + } +} diff --git a/tst/functions/assert/String/Should-NotBeString.Tests.ps1 b/tst/functions/assert/String/Should-NotBeString.Tests.ps1 new file mode 100644 index 000000000..fe0bf97de --- /dev/null +++ b/tst/functions/assert/String/Should-NotBeString.Tests.ps1 @@ -0,0 +1,54 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Get-StringNotEqualDefaultFailureMessage" { + It "returns correct default message" { + $expected = "Expected the strings to be different but they were the same 'abc'." + $actual = Get-StringNotEqualDefaultFailureMessage -Expected "abc" -Actual "abc" + $actual | Verify-Equal $expected + } + + It "Throws with default message when test fails" { + $expected = Get-StringNotEqualDefaultFailureMessage -Expected "abc" -Actual "abc" + $exception = { Should-NotBeString -Expected "abc" -Actual "abc" } | Verify-AssertionFailed + "$exception" | Verify-Equal $expected + } + } +} + +Describe "Should-NotBeString" { + It "Does nothing when string are different" { + Should-NotBeString -Expected "abc" -Actual "bde" + } + + It "Throws when strings are the same" { + { Should-NotBeString -Expected "abc" -Actual "abc" } | Verify-AssertionFailed + } + + It "Allows actual to be passed from pipeline" { + "abc" | Should-NotBeString -Expected "bde" + } + + It "Allows expected to be passed by position" { + Should-NotBeString "abc" -Actual "bde" + } + + It "Allows actual to be passed by pipeline and expected by position" { + "abc" | Should-NotBeString "bde" + } + + Context "String specific features" { + It "Can compare strings in CaseSensitive mode" { + Should-NotBeString -Expected "ABC" -Actual "abc" -CaseSensitive + } + + It "Can compare strings without whitespace" { + { Should-NotBeString -Expected " a b c " -Actual "abc" -IgnoreWhitespace } | Verify-AssertionFailed + } + } + + It "Can be called with positional parameters" { + { Should-NotBeString "a" "a" } | Verify-AssertionFailed + } +} + diff --git a/tst/functions/assert/Time/Should-BeAfter.Tests.ps1 b/tst/functions/assert/Time/Should-BeAfter.Tests.ps1 new file mode 100644 index 000000000..83466511b --- /dev/null +++ b/tst/functions/assert/Time/Should-BeAfter.Tests.ps1 @@ -0,0 +1,59 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeAfter" { + It "Does not throw when actual date is before expected date" -ForEach @( + @{ Actual = [DateTime]::Now.AddDays(1); Expected = [DateTime]::Now } + ) { + $Actual | Should-BeAfter -Expected $Expected + } + + It "Does not throw when actual date is before expected date using ago" { + [DateTime]::Now.AddMinutes(11) | Should-BeAfter 10minutes -Ago + } + + It "Does not throw when actual date is before expected date using fromNow" { + [DateTime]::Now.Add([timespan]::FromMinutes(20)) | Should-BeAfter 10minutes -FromNow + } + + It "Does not throw when actual date is before expected date using Now" { + [DateTime]::Now.Add([timespan]::FromMinutes(20)) | Should-BeAfter -Now + } + + It "Does not throw when actual date is before expected date using Now parameter set but not providing any switch" { + [DateTime]::Now.Add([timespan]::FromMinutes(20)) | Should-BeAfter + } + + It "Throws when actual date is before expected date" -ForEach @( + @{ Actual = [DateTime]::Now.AddDays(-1); Expected = [DateTime]::Now } + ) { + { $Actual | Should-BeAfter -Expected $Expected } | Verify-AssertionFailed + } + + It "Throws when actual date is before expected date using ago" { + { [DateTime]::Now.AddMinutes(-11) | Should-BeAfter 10minutes -Ago } | Verify-AssertionFailed + } + + It "Throws when actual date is before expected date using fromNow" { + { [DateTime]::Now.Add([timespan]::FromMinutes(9)) | Should-BeAfter 10minutes -FromNow } | Verify-AssertionFailed + } + + It "Throws when actual date is before expected date using Now" { + { [DateTime]::Now.Add([timespan]::FromMinutes(-1)) | Should-BeAfter -Now } | Verify-AssertionFailed + } + + It "Throws when actual date is before expected date using Now parameter set but not providing any switch" { + { [DateTime]::Now.Add([timespan]::FromMinutes(-1)) | Should-BeAfter } | Verify-AssertionFailed + } + + It "Throws when both -Ago and -FromNow are used" -ForEach @( + ) { + { $Actual | Should-BeAfter 10minutes -Ago -FromNow } | Verify-Throw + } + + It "Can check file creation date" { + New-Item -ItemType Directory -Path "TestDrive:\MyFolder" -Force | Out-Null + $path = "TestDrive:\MyFolder\test.txt" + "hello" | Set-Content $path + (Get-Item $path).CreationTime | Should-BeAfter 1s -Ago + } +} diff --git a/tst/functions/assert/Time/Should-BeBefore.Tests.ps1 b/tst/functions/assert/Time/Should-BeBefore.Tests.ps1 new file mode 100644 index 000000000..561ce734b --- /dev/null +++ b/tst/functions/assert/Time/Should-BeBefore.Tests.ps1 @@ -0,0 +1,61 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeBefore" { + It "Does not throw when actual date is before expected date" -ForEach @( + @{ Actual = [DateTime]::Now.AddDays(-1); Expected = [DateTime]::Now } + ) { + $Actual | Should-BeBefore -Expected $Expected + } + + It "Does not throw when actual date is before expected date using ago" { + [DateTime]::Now.AddMinutes(-11) | Should-BeBefore 10minutes -Ago + } + + It "Does not throw when actual date is before expected date using fromNow" { + [DateTime]::Now.Add([timespan]::FromMinutes(-20)) | Should-BeBefore 10minutes -FromNow + } + + It "Does not throw when actual date is before expected date using Now" { + [DateTime]::Now.Add([timespan]::FromMinutes(-20)) | Should-BeBefore -Now + } + + It "Does not throw when actual date is before expected date using Now parameter set but not providing any switch" { + [DateTime]::Now.Add([timespan]::FromMinutes(-20)) | Should-BeBefore + } + + It "Throws when actual date is after expected date" -ForEach @( + @{ Actual = [DateTime]::Now.AddDays(1); Expected = [DateTime]::Now } + ) { + { $Actual | Should-BeBefore -Expected $Expected } | Verify-AssertionFailed + } + + It "Throws when actual date is after expected date using ago" { + { [DateTime]::Now.AddMinutes(-9) | Should-BeBefore 10minutes -Ago } | Verify-AssertionFailed + } + + It "Throws when actual date is after expected date using fromNow" { + { [DateTime]::Now.Add([timespan]::FromMinutes(11)) | Should-BeBefore 10minutes -FromNow } | Verify-AssertionFailed + } + + It "Throws when actual date is after expected date using Now" { + { [DateTime]::Now.Add([timespan]::FromMinutes(20)) | Should-BeBefore -Now } | Verify-AssertionFailed + } + + It "Throws when actual date is after expected date using Now parameter set but not providing any switch" { + { [DateTime]::Now.Add([timespan]::FromMinutes(20)) | Should-BeBefore } | Verify-AssertionFailed + } + + It "Throws when both -Ago and -FromNow are used" -ForEach @( + ) { + { $Actual | Should-BeBefore 10minutes -Ago -FromNow } | Verify-Throw + } + + It "Can check file creation date" { + New-Item -ItemType Directory -Path "TestDrive:\MyFolder" -Force | Out-Null + $path = "TestDrive:\MyFolder\test.txt" + "hello" | Set-Content $path + # DateTime.Now is not precise in Windows Powershell. We need to wait a bit. + Start-Sleep -Milliseconds 15 + (Get-Item $path).CreationTime | Should-BeBefore -Now + } +} diff --git a/tst/functions/assert/Time/Should-BeFasterThan.Tests.ps1 b/tst/functions/assert/Time/Should-BeFasterThan.Tests.ps1 new file mode 100644 index 000000000..ac66698cc --- /dev/null +++ b/tst/functions/assert/Time/Should-BeFasterThan.Tests.ps1 @@ -0,0 +1,50 @@ +Set-StrictMode -Version Latest + +InPesterModuleScope { + Describe "Get-TimeSpanFromStringWithUnit" { + It "Throws when string is not a valid timespan string" { + { Get-TimeSpanFromStringWithUnit 1f } | Verify-Throw + } + + It "Parses string with units correctly" -ForEach @( + @{ Value = "1ms"; Expected = [timespan]::FromMilliseconds(1) } + @{ Value = "1mil"; Expected = [timespan]::FromMilliseconds(1) } + @{ Value = "1s"; Expected = [timespan]::FromSeconds(1) } + @{ Value = "1m"; Expected = [timespan]::FromMinutes(1) } + @{ Value = "1h"; Expected = [timespan]::FromHours(1) } + @{ Value = "1d"; Expected = [timespan]::FromDays(1) } + @{ Value = "1w"; Expected = [timespan]::FromDays(7) } + @{ Value = "1sec"; Expected = [timespan]::FromSeconds(1) } + @{ Value = "1second"; Expected = [timespan]::FromSeconds(1) } + @{ Value = "1.5hours"; Expected = [timespan]::FromHours(1.5) } + ) { + Get-TimeSpanFromStringWithUnit -Value $Value | Verify-Equal -Expected $Expected + } + } +} + +Describe "Should-BeFasterThan" { + It "Does not throw when actual is faster than expected" -ForEach @( + @{ Actual = { Start-Sleep -Milliseconds 10 }; Expected = "100ms" } + ) { + $Actual | Should-BeFasterThan -Expected $Expected + } + + It "Does not throw when actual is faster than expected taking TimeSpan" -ForEach @( + @{ Actual = [timespan]::FromMilliseconds(999); Expected = "1s" } + ) { + $Actual | Should-BeFasterThan -Expected $Expected + } + + It "Throws when scriptblock is slower than expected" -ForEach @( + @{ Actual = { Start-Sleep -Milliseconds 10 }; Expected = "1ms" } + ) { + { $Actual | Should-BeFasterThan -Expected $Expected } | Verify-AssertionFailed + } + + It "Throw timespan is longer than expected" -ForEach @( + @{ Actual = [timespan]::FromMilliseconds(999); Expected = "1ms" } + ) { + { $Actual | Should-BeFasterThan -Expected $Expected } | Verify-AssertionFailed + } +} diff --git a/tst/functions/assert/Time/Should-BeSlowerThan.Tests.ps1 b/tst/functions/assert/Time/Should-BeSlowerThan.Tests.ps1 new file mode 100644 index 000000000..1f2179d72 --- /dev/null +++ b/tst/functions/assert/Time/Should-BeSlowerThan.Tests.ps1 @@ -0,0 +1,27 @@ +Set-StrictMode -Version Latest + +Describe "Should-BeSlowerThan" { + It "Does not throw when actual is slower than expected" -ForEach @( + @{ Actual = { Start-Sleep -Milliseconds 10 }; Expected = "1ms" } + ) { + $Actual | Should-BeSlowerThan -Expected $Expected + } + + It "Does not throw when actual is slower than expected taking TimeSpan" -ForEach @( + @{ Actual = [timespan]::FromMilliseconds(999); Expected = "1ms" } + ) { + $Actual | Should-BeSlowerThan -Expected $Expected + } + + It "Throws when scriptblock is faster than expected" -ForEach @( + @{ Actual = { Start-Sleep -Milliseconds 10 }; Expected = "1000ms" } + ) { + { $Actual | Should-BeSlowerThan -Expected $Expected } | Verify-AssertionFailed + } + + It "Throw timespan is shorter than expected" -ForEach @( + @{ Actual = [timespan]::FromMilliseconds(10); Expected = "1000ms" } + ) { + { $Actual | Should-BeSlowerThan -Expected $Expected } | Verify-AssertionFailed + } +}