Reputation: 1690
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
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
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