Practical PowerShell Unit-Testing: Checking program flow

Comments 0

Share to social media

Contents

Part one of this series explained how to install and use Pester, and the basic structure of unit tests in PowerShell. Part two covered mocking and parameterized test cases. This final installment focuses on validation, both with data and with program flow.

Validating in a Test

I have touched on actual validation steps only implicitly in some of the examples thus far. This section describes the important Should command (see the Pester reference page at https://github.com/pester/Pester/wiki/Should).  The canonical verification assertion is:

Should offers a rich selection of operators, as detailed in the following two tables.

Element

Case Insensitive

Case Sensitive

Test that objects are the same

Be

BeExactly

Test that objects match by regex

Match

MatchExactly

Test that a file contains an item

Contain

ContainExactly

 

Element

Operator

 

Test for null or empty string

BeNullOrEmpty

 

Test for object existence (akin to Test-Path)

Exist

 

Test whether an exception was thrown

Throw

 

Negate any of the above operators

Not

 

Note that earlier Pester versions-and earlier articles written about it-showed this syntax, which is no longer valid:

2084-pencil.jpg

Use the Should command to validate scalar data.

Call History

Besides validating that you get back an expected value, the next most common thing to validate in a unit test is that a function g is called as a result of your calling a function f. Just as with standard .NET mocking framework moq (or others of its ilk), you can verify whether a function or cmdlet was called in PowerShell by using Pester to mock it, run your action, then check whether the mock was called. Here is the syntax for asserting that a mock was called:

The simplest case is checking whether a mock was called or not, regardless of the arguments passed to the function, and regardless of how many times it was called, use this simple form:

Contrariwise, if you wish to assert that a mock was never called, use the -Times parameter, specifying that it should be called zero times, hence not at all:

It is important to note that the count you supply to -Times indicates that the mock must be called at least that many times.  Thus, to assert that a mock was called but only once, add the -Exactly parameter:

Besides examining how often a mock was called, you can also check how a mock was called, i.e. with what parameters. Say, for example, you wanted to confirm that Select-String was invoked with the SimpleMatch switch parameter:

When validating that functions or cmdlets were called, it is important to consider scope. Say, for example, that you have multiple test (It) blocks inside a Context block, as shown. You also declare a mock inside the Context scope but outside of either It scope:

You could (correctly) surmise that checking call history with Assert-MockCalled would fail in one or both tests because they would both be adding to the same mock’s history.

But now consider moving the mock inside the test scope (the It block), as shown below.  Notice that each It block specifies the same mock.

You would think the tests would now be completely independent-and correctly validate the mock calls-but not so!  The mocks are still scoped to the Context block!  See my forum post on this important point. It turns out that, as designed, Assert-MockCalled is scoped to its containing Context block (or if no Context block is present, then its current Describe block), which is why the above tests will not work correctly. I could see that that behavior is useful for some tests, but for others (like the example above) it would really be useful to be able to scope a mock to an It block. As Dave Wyatt responded in my post referenced above, the new 3.0 release now provides just this flexibility: the -Scope parameter. Thus, we need only change each Assert-MockCalled above to specify scoping to the It block…

… for the above two tests in the same Context block to correctly validate calls on the mock. I would recommend explicitly using the Scope parameter (which may be set to It, Context, or Describe) on all Assert-MockCalled instances for clarity.

Pester 3.0 note: Version 3.0 is brand new as I write so I have only experimented with it for a short time. According to the documentation, a defined mock is still supposed to default to the closest Context or Describe block, but my cursory test seems to show that a mock actually defaults to the block in which it is defined, be it It, Context, or Describe. I found this out because my tests that had failed with 2.0 started working with 3.0 before I even added a -Scope It.

There is one other command available for verifying call history: Assert-VerifiableMocks. With this command, you flag each mock you wish to verify when you create the mocks. Then you invoke Assert-VerifiableMocks at the end of your test to validate all the mocks you have flagged in one go. See the official Pester documentation for more.

2084-pencil.jpg

Use Assert-MockCalled or Assert-VerifiableMocks to validate program flow.

Validating Array Data

Earlier you saw how to validate results with the Should command. Unfortunately, Should does not always work for arrays. Consider the following tests: