Day 48: Pester–Working With Assertions

Welcome to Day 48 of the “100 Days of DevOps with PowerShell”! For background on our goals in this series, see Announcing the “100 Days of Devops with PowerShell” Series here at SCC.

In my previous post, I introduced you to Pester, a framework for running unit tests to execute and validate PowerShell commands inside PowerShell. In this post, I’m going to dive a little deeper into Pester, looking again at the Describe and It statements, but also discussing the Context statement, and the different assertions available with the It command.

For this post, I will be using the following PowerShell script, MyCalculator.ps1:

 

Describe statement

A Pester unit test file can contain multiple Describe statements. The Describe statement is used to create a logical grouping of tests.  Each Describe block can contain any number of Context and It blocks, which I will talk about shortly.  For my Add function, above, I’ve created this initial unit test file:

It contains two Describe statements, one for what I consider my main tests, and second for my edge cases.  When I run Invoke-Pester to execute my tests, I get the following results:

image

I can see each Describe block, and the associated unit tests that have run inside that block. Organizing tests inside of Describe blocks is important, as Invoke-Pester has a flag allowing me to execute a specific block of code:

image

I consider it a best practice to give the Describe block a descriptive name or phrase, to easily understand what is being tested by the block.

Context statement

The Context statement is used to provide a logical grouping of It statements in a Describe block.  A Describe block can contain multiple It statements, and being able to group those statements together can make it easier to understand the tests. There are also advanced features to Pester (such as the ability to mock functions and define setup/tear down tasks that happen before each test) that I will discuss in a future post related to the Context statement.

Here is my modified unit test script, using Context blocks:

And here is the output from running Invoke-Pester:

image

The tests are now broken down by both Describe blocks and Context Blocks.

It and Should statements

The It statement can only be used inside either a Describe or a Context block, and is used to assert a single expectation or test result. If the test is a failure, then the It statement should throw an exception. This is normally done using the Should statement, which allows assertions to be written in plain language that is easily understandable. As a best practice, the name of the It block should describe what the test does. Take the following test:

This unit test is testing adding two positive numbers together. First, I define my It block with a descriptive name.  The first statement of the It block calls the function I’m testing and passes in two positive number parameters. The second statement takes the result, and uses the Should statement to say that the result should equal 8.  Remember, this is unit testing, not functional testing. I want to test discrete chunks of my script, not make sure the entire script works correctly from start to finish.

The Should command provides a natural language way to determine whether a test passes or fails, and to throw an exception when the test fails.  There are nine different operators that can be used with the Should command:

Operator Name

Description

Be Compares one object with another for equality and throws an exception if the two objects are not the same. This is not a case-sensitive comparison.
BeExactly Compares one object with another for equality and throws an exception if the two objects are not the same. This is a case-sensitive comparison.
Exist Does not perform any comparison but checks to see if the object calling Exists is actually present in a PowerShell Provider.
Contain Checks to see if a file contains the specified text. This is not a case-sensitive search, and regular expressions can be used.
ContainsExactly Checks to see if a file contains the specified text. This is a case-sensitive search and can use regular expressions.
Match Uses a regular expression to perform a non-case-sensitive compare of two objects
MatchExactly Uses a regular expression to perform a case-sensitive compare of two objects
Throw Checks if an exception was thrown in the input Script block
BeNullOrEmpty Checks values for null or empty strings.

I’ve modified my unit tests as follows:

Notice the first two It statements test the same values, but one statement uses the Be operator, while the other uses the Match operator (in this case, two ways to skin the same cat).  I also intentionally have created a test that fails. When I execute these unit tests using Invoke-Pester:

image

I can see that two of my tests have passed successfully, but the test located in the “Failing Test” context did not. Notice how, by naming the test with a descriptive name, I can easily tell that this is intentional.

Next post, I’ll dive even deeper, showing how we can specify specific setup and teardown tasks for tests, as well as how to mock the behavior of an existing command with an alternative implementation.

Previous Installments

To see the previous installments in this series, visit “100 Days of DevOps with PowerShell”.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.