Summary: Guest blogger, Dave Wyatt, discusses using Pester to analyze small pieces of Windows PowerShell code.
Note This is a five-part series that includes the following posts:
Before we get into the technical details today, let’s define a few terms. There are several categories of automated testing, all of which are used in a continuous delivery pipeline. For the purposes of Windows PowerShell code, I tend to refer to three:
A unit test is the smallest and fastest type, and it is the first thing that will be run in your pipeline. It can usually be executed on the developer’s machine before checking in the code to source control. Unit tests are responsible for verifying the behavior of a single unit of code, which in PowerShell, typically means a function.
If the function you are testing depends on anything other than the parameters that are passed in, you’ll want to isolate your code from those other dependencies in the unit test. These dependencies could be anything, including a web service of some sort, the state of the computer where the script runs, or even calls to other PowerShell functions or cmdlets.
Integration tests are pretty much the opposite of unit tests. Instead of isolating your code from the other parts, you run everything in all its glory, and make sure it’s behaving properly when all of the bits are working together. This might require you to set up a more complicated test environment, so the necessary web services, for example, are available. This means that integration tests are more expensive to run. It’s for this reason that you only move on to integration tests if the unit tests have already passed.
Acceptance tests are fundamentally the same as integration tests, except they refer to tests that you run after your code is deployed into production. You can use them as a quick sanity check to make sure everything’s up and running. You might also hear these referred to as “smoke tests.”
The examples of Pester that you’ve seen so far are perfect for integration or acceptance tests. They make calls to an API (without caring what its internals do), and write test cases against the results. For a unit test, though, we need some way of isolating our function under test from its dependencies. That’s where mocking comes into play.
Pester has a built-in mocking framework that allows you to create dummy versions of other PowerShell commands. A picture is worth a thousand words, so here’s a sample of a PowerShell function that needs some mocking to be properly unit tested:
This is a fairly simple function, but it contains calls to four other cmdlets: Get-Date, Get-ADUser, Where-Object, and Disable-ADAccount. Of these, we can ignore Where-Object because it’s simply a logic construct—and indeed, it is what we’re testing. (The function is written this way instead of using the better Search-ADAccount cmdlet simply to give us more to work within the Pester examples.)
To test this code without requiring an actual Active Directory domain, we need to consider a few things:
Here’s what this might look like:
In this example, we’re seeing two new Pester commands: Mock and Assert-MockCalled. When you use Mock, you temporarily replace the behavior of a PowerShell command with whatever you want it to be. Mock is active only inside the Describe or Context block where it is defined. If we had another Describe block in the example, Get-Date, Get-ADUser, and Disable-ADAccount would go back to their normal behavior. When Mock was called on Disable-ADAccount, we didn’t even bother to specify an implementation; it defaults to doing nothing.
In my experience, most mocks fall into one of these two categories: You either want them to return objects that your tests define, or you want them to do nothing (and then later verify how they were called). I can’t think of a situation where I’ve had a mock return output later and be verified via the Assert-MockCalled command because those tests would be redundant.
There are a few things to notice about mocking…
It’s important to understand how this works, because your scripts might need some small modifications to work well with mocking in Pester. For example, let’s revise our Active Directory example slightly:
In this version, the calls to Get-ADUser and Disable-ADAccount have been module-qualified (in ActiveDirectory\Get-ADUser). This gives PowerShell some extra information about which command to execute, and causes it to skip your mocked command and execute the live commands instead.
To make the code friendly to mocking, you’d need to remove the module-qualified calls. Or you can wrap those calls in your own internal function, which can be mocked instead of the Active Directory cmdlets. Wrapping code into a function to facilitate mocking is a pretty common occurrence, such as if you want to mock calls to a .NET class or method (which Pester can’t do).
There are other nuances to the Mock and Assert-MockCalled commands, some of which we’ll talk about tomorrow when we look at unit testing code in script modules. To find more help and information about Pester, see:
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at email@example.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.