Skip to content

Commit b577d1b

Browse files
authored
Add Assert assertions to Pester (#2428)
Add assertions from Assert and improve them.
1 parent 1a3cad0 commit b577d1b

File tree

110 files changed

+7266
-83
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+7266
-83
lines changed

build.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,12 @@ $script = @(
277277
"$PSScriptRoot/src/Pester.Runtime.ps1"
278278
"$PSScriptRoot/src/TypeClass.ps1"
279279
"$PSScriptRoot/src/Format.ps1"
280+
# 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.
281+
"$PSScriptRoot/src/Format2.ps1"
280282
"$PSScriptRoot/src/Pester.RSpec.ps1"
281283
"$PSScriptRoot/src/Main.ps1"
282284

285+
"$PSScriptRoot/src/functions/assert/*/*"
283286
"$PSScriptRoot/src/functions/assertions/*"
284287
"$PSScriptRoot/src/functions/*"
285288

docs/assertion-types.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Assert assertions
2+
3+
Pester 6 preview comes with a new set of Should-* assertions. These new assertions are split these categories based on their usage:
4+
5+
- value
6+
- generic
7+
- type specific
8+
9+
- collection
10+
- generic
11+
- combinator
12+
13+
Each of these categories treats `$Actual` and `$Expected` values differently, to provide a consistent behavior when using the `|` syntax.
14+
15+
## Value vs. Collection assertions
16+
17+
The `$Actual` value can be provided by two syntaxes, either by pipeline (`|`) or by parameter (`-Actual`):
18+
19+
```powershell
20+
1 | Should-Be -Expected 1
21+
Should-Be -Actual 1 -Expected 1
22+
```
23+
24+
### Using pipeline syntax
25+
26+
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:
27+
28+
```powershell
29+
1 | Should-Be
30+
@(1) | Should-Be
31+
```
32+
33+
These will both be received by the assertion as `@(1)`.
34+
35+
For this reason a value assertion will handle this as `1`, but a collection assertion will handle this input as `@(1)`.
36+
37+
Another special case is `@()`. A value assertion will handle it as `$null`, but a collection assertion will handle it as `@()`.
38+
39+
`$null` remains `$null` in both cases.
40+
41+
```powershell
42+
# Should-Be is a value assertion:
43+
1 | Should-Be -Expected 1
44+
@(1) | Should-Be -Expected 1
45+
$null | Should-Be -Expected $null
46+
@() | 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.
47+
# $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?
48+
49+
# This fails, because -Expected does not allow collections.
50+
@() | Should-Be -Expected @()
51+
52+
53+
54+
```powershell
55+
# Should-BeCollection is a collection assertion:
56+
1 | Should-BeCollection -Expected @(1)
57+
@(1) | Should-BeCollection -Expected @(1)
58+
@() | Should-BeCollection -Expected @()
59+
60+
# This fails, because -Expected requires a collection.
61+
$null | Should-BeCollection -Expected $null
62+
```
63+
64+
### Using the -Actual syntax
65+
66+
The value provides to `-Actual`, is always exactly the same as provided.
67+
68+
```powershell
69+
Should-Be -Actual 1 -Expected 1
70+
71+
# This fails, Actual is collection, while expected is int.
72+
Should-Be -Actual @(1) -Expected 1
73+
```
74+
75+
## Value assertions
76+
77+
### Generic value assertions
78+
79+
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`.
80+
81+
The `$Expected` accepts any input that is not a collection.
82+
The type of `$Expected` determines the type to be used for the comparison.
83+
`$Actual` is automatically converted to that type.
84+
85+
```powershell
86+
1 | Should-Be -Expected $true
87+
Get-Process -Name Idle | Should-Be -Expected "System.Diagnostics.Process (Idle)"
88+
```
89+
90+
The assertions in the above examples will both pass:
91+
- `1` converts to `bool` `$true`, which is the expected value.
92+
- `Get-Process` retrieves the Idle process (on Windows). This process object gets converted to `string`. The string is equal to the expected value.
93+
94+
### Type specific value assertions
95+
96+
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`.
97+
98+
The `$Expected` accepts input that has the same type as the assertion type. E.g. `Should-BeString -Expected "my string"`.
99+
100+
`$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`.
101+
102+
## Collection assertions
103+
104+
105+
106+
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.

global.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"sdk": {
33
"rollForward": "latestFeature",
4-
"version": "8.0.100"
4+
"version": "8.0.100",
5+
"allowPrerelease": false
56
}
67
}

src/Format2.ps1

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
function Format-Collection2 ($Value, [switch]$Pretty) {
2+
$length = 0
3+
$o = foreach ($v in $Value) {
4+
$formatted = Format-Nicely2 -Value $v -Pretty:$Pretty
5+
$length += $formatted.Length + 1 # 1 is for the separator
6+
$formatted
7+
}
8+
9+
$prettyLimit = 50
10+
if ($Pretty -and ($length + 3) -gt $prettyLimit) {
11+
# 3 is for the '@()'
12+
"@(`n $($o -join ",`n ")`n)"
13+
}
14+
else {
15+
"@($($o -join ', '))"
16+
}
17+
}
18+
19+
function Format-Object2 ($Value, $Property, [switch]$Pretty) {
20+
if ($null -eq $Property) {
21+
$Property = foreach ($p in $Value.PSObject.Properties) { $p.Name }
22+
}
23+
$orderedProperty = foreach ($p in $Property | & $SafeCommands['Sort-Object']) {
24+
# force the values to be strings for powershell v2
25+
"$p"
26+
}
27+
28+
$valueType = Get-ShortType $Value
29+
$items = foreach ($p in $orderedProperty) {
30+
$v = ([PSObject]$Value.$p)
31+
$f = Format-Nicely2 -Value $v -Pretty:$Pretty
32+
"$p=$f"
33+
}
34+
35+
if (0 -eq $Property.Length ) {
36+
$o = "$valueType{}"
37+
}
38+
elseif ($Pretty) {
39+
$o = "$valueType{`n $($items -join ";`n ");`n}"
40+
}
41+
else {
42+
$o = "$valueType{$($items -join '; ')}"
43+
}
44+
45+
$o
46+
}
47+
48+
function Format-String2 ($Value) {
49+
if ('' -eq $Value) {
50+
return '<empty>'
51+
}
52+
53+
"'$Value'"
54+
}
55+
56+
function Format-Null2 {
57+
'$null'
58+
}
59+
60+
function Format-Boolean2 ($Value) {
61+
'$' + $Value.ToString().ToLower()
62+
}
63+
64+
function Format-ScriptBlock2 ($Value) {
65+
'{' + $Value + '}'
66+
}
67+
68+
function Format-Number2 ($Value) {
69+
[string]$Value
70+
}
71+
72+
function Format-Hashtable2 ($Value) {
73+
$head = '@{'
74+
$tail = '}'
75+
76+
$entries = foreach ($v in $Value.Keys | & $SafeCommands['Sort-Object']) {
77+
$formattedValue = Format-Nicely2 $Value.$v
78+
"$v=$formattedValue"
79+
}
80+
81+
$head + ( $entries -join '; ') + $tail
82+
}
83+
84+
function Format-Dictionary2 ($Value) {
85+
$head = 'Dictionary{'
86+
$tail = '}'
87+
88+
$entries = foreach ($v in $Value.Keys | & $SafeCommands['Sort-Object'] ) {
89+
$formattedValue = Format-Nicely2 $Value.$v
90+
"$v=$formattedValue"
91+
}
92+
93+
$head + ( $entries -join '; ') + $tail
94+
}
95+
96+
function Format-Nicely2 ($Value, [switch]$Pretty) {
97+
if ($null -eq $Value) {
98+
return Format-Null2 -Value $Value
99+
}
100+
101+
if ($Value -is [bool]) {
102+
return Format-Boolean2 -Value $Value
103+
}
104+
105+
if ($Value -is [string]) {
106+
return Format-String2 -Value $Value
107+
}
108+
109+
if ($value -is [type]) {
110+
return Format-Type2 -Value $Value
111+
}
112+
113+
if (Is-DecimalNumber -Value $Value) {
114+
return Format-Number2 -Value $Value
115+
}
116+
117+
if (Is-ScriptBlock -Value $Value) {
118+
return Format-ScriptBlock2 -Value $Value
119+
}
120+
121+
if (Is-Value -Value $Value) {
122+
return $Value
123+
}
124+
125+
if (Is-Hashtable -Value $Value) {
126+
return Format-Hashtable2 -Value $Value
127+
}
128+
129+
if (Is-Dictionary -Value $Value) {
130+
return Format-Dictionary2 -Value $Value
131+
}
132+
133+
if ((Is-DataTable -Value $Value) -or (Is-DataRow -Value $Value)) {
134+
return Format-DataTable2 -Value $Value -Pretty:$Pretty
135+
}
136+
137+
if (Is-Collection -Value $Value) {
138+
return Format-Collection2 -Value $Value -Pretty:$Pretty
139+
}
140+
141+
Format-Object2 -Value $Value -Property (Get-DisplayProperty2 $Value.GetType()) -Pretty:$Pretty
142+
}
143+
144+
function Get-DisplayProperty2 ([Type]$Type) {
145+
# rename to Get-DisplayProperty?
146+
147+
<# some objects are simply too big to show all of their properties,
148+
so we can create a list of properties to show from an object
149+
maybe the default info from Get-FormatData could be utilized here somehow
150+
so we show only stuff that would normally show in format-table view
151+
leveraging the work PS team already did #>
152+
153+
# this will become more advanced, basically something along the lines of:
154+
# foreach type, try constructing the type, and if it exists then check if the
155+
# incoming type is assignable to the current type, if so then return the properties,
156+
# this way I can specify the map from the most concrete type to the least concrete type
157+
# and for types that do not exist
158+
159+
$propertyMap = @{
160+
'System.Diagnostics.Process' = 'Id', 'Name'
161+
}
162+
163+
$propertyMap[$Type.FullName]
164+
}
165+
166+
function Get-ShortType2 ($Value) {
167+
if ($null -ne $value) {
168+
Format-Type2 $Value.GetType()
169+
}
170+
else {
171+
Format-Type2 $null
172+
}
173+
}
174+
175+
function Format-Type2 ([Type]$Value) {
176+
if ($null -eq $Value) {
177+
return '[null]'
178+
}
179+
180+
$type = [string]$Value
181+
182+
$typeFormatted = $type `
183+
-replace "^System\." `
184+
-replace "^Management\.Automation\.PSCustomObject$", "PSObject" `
185+
-replace "^PSCustomObject$", "PSObject" `
186+
-replace "^Object\[\]$", "collection" `
187+
188+
"[$($typeFormatted)]"
189+
}
190+
191+
function Format-DataTable2 ($Value) {
192+
return "$Value"
193+
}
194+

0 commit comments

Comments
 (0)