PowerShell How-To

PowerShell Pester Test Design: Best Practices

The Pester PowerShell module allows for some flexibility in how tests are written, but as Adam notes, there are still a few key points to consider.

Have you ever wanted to verify that a PowerShell script actually did what you thought it did automatically? I'm not talking eyeballing the results and considering it good, but actually running code afterward to ensure everything ran correctly.

If so and you haven't been using Pester tests, you're missing out! Pester is a PowerShell module built by the community that allows scripters to perform tests against their PowerShell code and also their infrastructure.

Pester is written in PowerShell and thus is pretty flexible in how tests can be written, but there are still some best practices that should be taken into consideration when doing so.

Before we get too far, we first need to break apart what I mean by "tests." In the software testing world, we've got two kinds of tests: unit and integration. Barring any kind of community backlash, it's prudent of me to say that's not all of the types, but it's sufficient for us. Unit tests test code execution while integration tests test the result of that code. I point this out because they are two distinct areas of focus and vary greatly on best practices. For now, we're going to focus on unit tests.

A typical Pester test consists of a describe , an optional context block and one or more it blocks. An example of this is below:

describe 'My test' { context 'when the code is in X state' { it 'should return this thing' { } it 'should call this command' { } } }

Within any of these blocks, you're free to write whatever PowerShell you'd like, but it's not necessarily recommended to just throw whatever you want inside of there. It's important to follow some kind of best practice. I've been writing Pester tests for a long time now and have also written The Pester Book. I'm not saying I'm an expert, but these are the practices that I personally use that have helped me write great unit tests.

Execute Once and Assert Many Times

Using our code example above, let's say I'd like to test a script called Do-Thing . Before Pester can test anything, the code must be executed inside of the describe block somewhere. Where that is is up to you. I prefer to execute the code once and perform multiple assertions against that execution per context block.

describe 'My test' { context 'when the code is in X state' { $result = Do-Thing it 'should return this thing' { $result | should be 'thing' } it 'should call this command' { Assert-MockCalled -CommandName 'somecommand' } } }

I could have executed Do-Thing multiple times but there's no point. Inside of a context block, Pester can see everything.

Use Context Blocks as Actual Contexts

In Pester, a context block can be used for a lot of different purposes, but I've found the best use of context is to create them based on their actual name: context! For example, a piece of code can be executed under a number of different circumstances (contexts). It could be run when a file exists, when a registry key does not exist, when a server is up or down, et cetera. Notice a common theme? I'm using the word "when." I also start my context blocks with the word "when" because it will always indicate what state the environment is in.

Always Use the Exactly and Times Parameter on Assert-MockCalled

Assert-MockCalled is a Pester command that allows the user to check if a function inside of the code was invoked. It's an extremely useful command but is way too lenient when it's run with only the CommandName parameter. By default, when Assert-MockCalled is used only with the CommandName parameter, it will check if that command is invoked one, two, 10 or 100 times. It does not care. Asserting that the command is called is better than nothing at all, but you should make your test a little more specific.

For example, instead of doing this:

it 'should call this command' { Assert-MockCalled -CommandName 'somecommand' }

Do this:

it 'should call this command' { $assertParams = @{ CommandName = 'somecommand' Exactly = $true Times = 1 } Assert-MockCalled @assertParams }

Using both the Exactly and Times parameters with the Assert-MockCalled command gives the user much finer-grained control over how it's being executed. Be sure to always include the Exactly parameter because even if the extra step is taken to use the Times parameter, that only means one or more times. The Exactly parameter forces the number indicated by the Times parameter and nothing more.

There are lots of other best practices when designing Pester tests. For more information about Pester, check out all of the other Pester posts here on MCPmag.com or check out The Pester Book which covers test design and Pester A to Z.