2

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?

Charlie Joynt
  • 4,411
  • 1
  • 24
  • 46
  • 1
    `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 Apr 15 '16 at 16:31
  • 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 Apr 16 '16 at 19:53

2 Answers2

2

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
    }
  }
}
Community
  • 1
  • 1
Charlie Joynt
  • 4,411
  • 1
  • 24
  • 46
  • 1
    Good stuff (++). It's worth noting that `trap` handlers behave no differently from child scopes in PowerShell in general, with respect to variables: you can _read_ a variable from a parent (ancestral) scope, but if you _assign_ to such a variable by name only (without specifying a scope), you'll create a _local_ variable. – mklement0 Apr 16 '16 at 22:39
1

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.

Eris
  • 7,378
  • 1
  • 30
  • 45
  • 1
    Thanks for this answer; it is helpful (worth an upvote!) BUT doesn't quite solve the problem. Adding `continue` to the trap ensures that the ErrorRecord is suppressed. Combined with the comment from @PetSerAl above I now have a complete solution... – Charlie Joynt Apr 16 '16 at 19:56