Summary: Dave Wyatt discusses using Pester for testing PowerShell modules.
Note This is Part 4 of a five-part series. Also see:
- What is Pester and Why Should I Care?
- Getting Started with Pester
- Unit Testing PowerShell Code with Pester
Yesterday, we looked at how to use the Mock and Assert-MockCalled commands in Pester to unit test the logic in your PowerShell code without having any external dependencies. This can get a little bit trickier when you start to store your code in script modules (.psm1 files). To demonstrate this, I’ve taken the code from yesterday’s Active Directory example, placed it into a .psm1 file, and added a small change to introduce an internal function:
The Tests.ps1 file is fine as-is, with the small exception of calling Import-Module instead of dot-sourcing a .ps1 file on the first line. For a refresher, here’s what that tests file currently looks like:
Here’s what happens if I run Invoke-Pester on this file as-is:
When this happens to you, you may understandably start cursing the incompetent people who wrote this Pester thing. You clearly mocked the Get-ADUser command to return a set of test objects, so why the heck did the tests try to contact an actual Active Directory server?
But wait…
It gets even better. What happens if we try to mock or execute the internal GetUsersToDisable command instead?
It turns out that this is due to how PowerShell script modules work. Each module has its own private scope area where it can store its functions, variables, and aliases, which is entirely separate from the scope of the caller. When you call the Mock command in Pester, by default, it creates the mocked function in the scope of your Tests.ps1 script. Calls to the command from inside the module never see that mock because the module has its own scopes that are separate from the script.
Before you throw up your hands and give up on this Pester thing, let me introduce you to the InModuleScope command. It allows you to inject some or all of your test code into a script module, which gives you direct access to all of its internal bits and pieces. It also causes your mocks to be defined inside the module rather than in the Tests.ps1 script’s scope, solving all of the problems shown in the first two examples.
If you’re looking through the Help files in Pester, you may notice that there’s another way to accomplish a similar task: both the Mock and Assert-MockCalled commands have a –ModuleName parameter, which allows you to inject a mock into a module and then verify its calls. This was written before the InModuleScope command. However, it didn’t solve the problem of being able to call a module’s internal functions directly, so they can be unit tested separately from the exported functions.
The InModuleScope command is the newer and simpler way of accomplishing this. Any time I’m writing a set of tests for a script module, I write two lines at the top of the Tests.ps1 script to remove any existing copies of the module from the session and import the one I want to test. (The Remove-Module part can be important, because PowerShell will let you import two copies of the same module from different locations. Pester doesn’t like that at all.)
Everything else in the Tests.ps1 script gets wrapped in the InModuleScope command, as shown in the example, at which point I don’t need to worry about any problems. If you use this approach, all of the tests you’d expect to work with a simple .ps1 script will also work with a .psm1 script module.
With this last piece of the puzzle, you have all of the essential tools needed to write a comprehensive suite of Pester tests for any PowerShell code (or as tests for other code that can be invoked _from_ PowerShell).
Pester has a few more tricks up its sleeve to help you, though. Tomorrow, I’ll wrap up the series by demonstrating some of those features and by taking a look at some of the upcoming features planned for Pester 4.0.
~Dave
Thanks, Dave. Pester is an awesome tool!
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy