Functions/Assertions/PesterThrow.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
function PesterThrow([scriptblock] $ActualValue, $ExpectedMessage, $ErrorId, [switch] $Negate) {
    $script:ActualExceptionMessage = ""
    $script:ActualExceptionWasThrown = $false

    if ($null -eq $ActualValue) {
        throw (New-Object -TypeName ArgumentNullException -ArgumentList "ActualValue","Scriptblock not found. Input to 'Throw' and 'Not Throw' must be enclosed in curly braces.")
    }

    # This is superfluous, here for now.
    $ExpectedErrorId = $ErrorId

    try {
        do {
            $null = & $ActualValue
        } until ($true)
    } catch {
        $script:ActualExceptionWasThrown = $true
        $script:ActualExceptionMessage = $_.Exception.Message
        $script:ActualErrorId = $_.FullyQualifiedErrorId
        $script:ActualExceptionLine = Get-ExceptionLineInfo $_.InvocationInfo
    }

    [bool] $succeeded = $false

    if ($ActualExceptionWasThrown) {
        $succeeded = (Get-DoValuesMatch $script:ActualExceptionMessage $ExpectedMessage) -and
                     (Get-DoValuesMatch $script:ActualErrorId $ExpectedErrorId)
    }

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterThrowFailureMessage -ActualValue $ActualValue -ExpectedMessage $ExpectedMessage -ExpectedErrorId $ExpectedErrorId
        }
        else
        {
            $failureMessage = PesterThrowFailureMessage -ActualValue $ActualValue -ExpectedMessage $ExpectedMessage -ExpectedErrorId $ExpectedErrorId
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function Get-DoValuesMatch($ActualValue, $ExpectedValue) {
    #user did not specify any message filter, so any message matches
    if ($null -eq $ExpectedValue ) { return $true }

    return $ActualValue.ToString().IndexOf($ExpectedValue, [System.StringComparison]::InvariantCultureIgnoreCase) -ge 0
}

function Get-ExceptionLineInfo($info) {
    # $info.PositionMessage has a leading blank line that we need to account for in PowerShell 2.0
    $positionMessage = $info.PositionMessage -split '\r?\n' -match '\S' -join "`r`n"
    return ($positionMessage -replace "^At ","from ")
}

function PesterThrowFailureMessage($ActualValue, $ExpectedMessage, $ExpectedErrorId) {
    $StringBuilder = Microsoft.PowerShell.Utility\New-Object System.Text.StringBuilder
    $null = $StringBuilder.Append('Expected: the expression to throw an exception')

    if ($ExpectedMessage -or $ExpectedErrorId)
    {
        $null = $StringBuilder.Append(' with ')
        $Expected = switch ($null)
        {
            { $ExpectedMessage } { 'message {{{0}}}' -f $ExpectedMessage }
            { $ExpectedErrorId } { 'error id {{{0}}}' -f $ExpectedErrorId }
        }
        $Actual = switch ($null)
        {
            { $ExpectedMessage } { 'message was {{{0}}}' -f $ActualExceptionMessage }
            { $ExpectedErrorId } { 'error id was {{{0}}}' -f $ActualErrorId }
        }
        $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}`n {3}" -f
            ($Expected -join ' and '),
            @{$true="";$false="not "}[$ActualExceptionWasThrown],
            ($Actual -join ' and '),
            ($ActualExceptionLine  -replace "`n","`n ")
        ))
    }

    return $StringBuilder.ToString()
}

function NotPesterThrowFailureMessage($ActualValue, $ExpectedMessage, $ExpectedErrorId) {
    $StringBuilder = New-Object System.Text.StringBuilder
    $null = $StringBuilder.Append('Expected: the expression not to throw an exception')

    if ($ExpectedMessage -or $ExpectedErrorId)
    {
        $null = $StringBuilder.Append(' with ')
        $Expected = switch ($null)
        {
            { $ExpectedMessage } { 'message {{{0}}}' -f $ExpectedMessage }
            { $ExpectedErrorId } { 'error id {{{0}}}' -f $ExpectedErrorId }
        }
        $Actual = switch ($null)
        {
            { $ExpectedMessage } { 'message was {{{0}}}' -f $ActualExceptionMessage }
            { $ExpectedErrorId } { 'error id was {{{0}}}' -f $ActualErrorId }
        }
        $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}`n {3}" -f
            ($Expected -join ' and '),
            (@{$true="";$false="not "}[$ActualExceptionWasThrown]),
            ($Actual -join ' and '),
            ($ActualExceptionLine  -replace "`n","`n ")
        ))
    }
    else
    {
      $null = $StringBuilder.Append((". Message was {{{0}}}`n {1}" -f $ActualExceptionMessage, ($ActualExceptionLine -replace "`n","`n ")))
    }

    return $StringBuilder.ToString()
}

Add-AssertionOperator -Name Throw `
                      -Test $function:PesterThrow