2
votes

I'm wondering why I get the following behaviour when running this script. I have the script loaded in PowerShell ISE (v4 host) and have the Pester module loaded. I run the script by pressing F5.

function Test-Pester {
  throw("An error")
}

Describe "what happens when a function throws an error" {

  Context "we test with Should Throw" {

    It "Throws an error" {
      { Test-Pester } | Should Throw
    }
  }

  Context "we test using a try-catch construct" {

    $ErrorSeen = $false
    try {
      Test-Pester
    }
    catch {
      $ErrorSeen = $true
    }

    It "is handled by try-catch" {
      $ErrorSeen | Should Be $true
    }
  }

  Context "we test using trap" {

    trap {
      $ErrorSeen = $true
    }

    $ErrorSeen = $false

    Test-Pester

    It "is handled by trap" {
      $ErrorSeen | Should Be $true
    }
  }
}

I then get the following output:

Describing what happens when a function throws an error
   Context we test with Should Throw
    [+] Throws an error 536ms
   Context we test using a try-catch construct
    [+] is handled by try-catch 246ms
   Context we test using trap
An error
At C:\Test-Pester.ps1:2 char:7
+       throw("An error")
+       ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (An error:String) [], RuntimeException
    + FullyQualifiedErrorId : An error

    [-] is handled by trap 702ms
      Expected: {True}
      But was:  {False}
      at line: 40 in C:\Test-Pester.ps1
      40:           $ErrorSeen | Should Be $true

Question

Why is the trap{} apparently not running in the final test?

2
trap create new scope for error handler. trap { $ErrorSeen = $true } -> trap { ([ref]$ErrorSeen).Value = $true }. And this line: [-] is handled by trap 702ms indicates that Pester run corresponding test. Which it turn means that throw inside Test-Pester was handled (apparently by trap) and does not cause termination of Context block. - user4003407
Thanks. This does work. I have found a similar fix by explicitly setting the scope of $ErrorSeen to be either Global or Script. Presumably the same principle is at play with the scoping of trap {}. - Charlie Joynt

2 Answers

2
votes

Here are two solutions to the problem based on the comments/suggested answers from @PetSerAl and @Eris. I have also read and appreciated the answer given to this question:

Why are variable assignments within a Trap block not visible outside it?

Solution 1

Although variables set in the script can be read within the trap, whatever you do within the trap happens to a copy of that variable, i.e. only in the scope that is local to the trap. In this solution we evaluate a reference to $ErrorSeen so that when we set the value of the variable, we are actually setting the value of the variable that exists in the parent scope.

Adding a continue to the trap suppresses the ErrorRecord details, cleaning up the output of the test.

Describe "what happens when a function throws an error" {
  Context "we test using trap" {

    $ErrorSeen = $false

    trap {
      Write-Warning "Error trapped"
      ([Ref]$ErrorSeen).Value = $true
      continue
    }

    Test-Pester

    It "is handled by trap" {
      $ErrorSeen | Should Be $true
    }
  }
}

Solution 2

Along the same lines as the first solution, the problem can be solved by explicitly setting the scope of the $ErrorSeen variable (1) when it is first created and (2) when used within the trap {}. I have used the Script scope here, but Global also seems to work.

Same principle applies here: we need to avoid the problem where changes to the variable within the trap only happen to a local copy of the variable.

Describe "what happens when a function throws an error" {
  Context "we test using trap" {

    $Script:ErrorSeen = $false

    trap {
      Write-Warning "Error trapped"
      $Script:ErrorSeen = $true
      continue
    }

    Test-Pester

    It "is handled by trap" {
      $ErrorSeen | Should Be $true
    }
  }
}
1
votes

According to this blog, you need to tell your trap to do something to the control flow:

The [...] thing you notice is that when you run this as script, you will receive both your error message and the red PowerShell error message.

. 'C:\Scripts\test.ps1'
Something terrible happened!
Attempted to divide by zero.
At C:\Scripts\test.ps1:2 Char:3
+ 1/ <<<< null

This is because your Trap did not really handle the exception. To handle an exception, you need to add the "Continue" statement to your trap:

trap { 'Something terrible happened!'; continue }
1/$null

Now, the trap works as expected. It does whatever you specified in the trap script block, and PowerShell does not get to see the exception anymore. You no longer get the red error message.