Efie
Efie

Reputation: 1690

How can you mock an ErrorRecord with Pester in Powershell?

I have a function that I would like to include in my unit tests but I'm unable to figure out how. The method takes in an ErrorRecord object (from an Invoke-RestMethod call), grabs the status code and generates a custom error message. Here is what the first portion of the call looks like:

function Resolve-RestError {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $RequestError
    )
    $statusCode = $requestError.Exception.Response.StatusCode.value__
    switch ($statusCode) {

The problem that I am running into is in the recreation of the ErrorRecord object. I have spent many hours looking for a way to recreate the error object, but I am not able to recreate it exactly.

In the actual ErrorRecord object during execution, accessing the Response.StatusCode field returns a string with a description of the code. To access the actual value you need to call Response.StatusCode.value__. It seems that it's not possible to reconstruct an internal .value__ variable as this is done by the JIT compiler (from my understanding). Is there a way to either add a .value__ property to a custom object, or simply mock that variable during my tests?

Upvotes: 1

Views: 527

Answers (2)

Tydaeus
Tydaeus

Reputation: 1605

Ideally, you'd want a way to allow your fake ErrorRecord to survive the try-catch processing so that you could verify behavior in functions that catch it, not just in separate error-processing functions. From testing it looks like this means you do need a real [ErrorRecord] with its Exception set to a real [Exception]-derived object; otherwise, the Exception property gets converted to a string (discarding nested properties like value__).

It looks like this can be done by creating a custom subclass of Exception to allow you to populate the properties (which using WebException does not allow).

class FakeWebException : System.Exception {
    [PSCustomObject] $Response = [PSCustomObject]@{
        StatusCode = [PSCustomObject]@{
            value__ = 404
        }
    }
}

$fwe = [FakeWebException]::new()

$errorRecord = [System.Management.Automation.ErrorRecord]::new(
    $fwe,
    'UnspecifiedErrorId',
    ([System.Management.Automation.ErrorCategory]::NotSpecified),
    'UnspecifiedObject'
)

Now you have a real ErrorRecord containing your fake exception, so if you throw $errorRecord, processing in catch can successfully reference $_.Exception.Response.StatusCode.value__ (and any other properties you choose to add) and Pester assertions can verify behavior.

Note that due to PowerShell custom class scoping, the custom class will only be referenceable within the block that declares it, so you will want to write an initializer function in that block if you want to create FakeWebException instances in multiple places (e.g. function New-FakeWebException { return [FakeWebException]::new() }).

Upvotes: 0

Mark Wragg
Mark Wragg

Reputation: 23395

You're right it doesn't seem overly simple to recreate an ErrorRecord object. My goto for this sort of thing is to simulate the output I want to Mock in my test and then store that result via Export-CliXml but when trying this with an ErrorRecord you only get as far as Exception.Response and the rest is lost in the conversion (even when setting a -Depth value).

There might be a smarter way, but one workaround would be to just fake the object as follows (note you didn't provide your full Function so I just created a simplified version of it to prove it worked for this specific case):

function Resolve-RestError {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $RequestError
    )
    $statusCode = $RequestError.Exception.Response.StatusCode.value__

    Return $statusCode
}

Describe 'Resolve-RestError' {

    It 'Should handle a 404' {

        $ErrorRecord = [pscustomobject]@{ 
            Exception = @{
                Response = @{
                    StatusCode = @{
                        value__ = 404
                    }
                }
            }
        }
        
        Resolve-RestError -RequestError $ErrorRecord | Should -Be 404
    }
}

Upvotes: 1

Related Questions