- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
The robust unit-testing framework Pester has steadily been increasing in popularity. People are slowly starting to see the benefit of creating tests for their PowerShell scripts. Nearly all Pester documentation, though, references just that—scripts. However, Pester isn't limited to testing PS1 files; it also can quickly test modules (PSM1 files).
Pester needs to read the code in your script or module. Typically, when testing a script, you'll see lots of examples of dot sourcing the script to bring the code therein into the current session. This is to allow Pester to get into the functions declared in that script. Perhaps I have a script saved in the root of my C:\ called SampleScript.ps1 that contains a function I'd like to test, called Do-Something.
function Do-Something { Write-Output 'Do-Something output' } To do this, my initial test file might look something like this, with me dot sourcing the script and then defining the test. . C:\SampleScript.ps1 describe 'SampleScript' { it 'does this thing' { } }
I then run it with Invoke-Pester and get the following output:
That's great, but what if I want to test a module instead? I can't exactly dot source a module. I'll need to take a different approach. To test a module, I need to import it into the current session, although it's not quite that easy. First, I need to remove that module from the session if it's already loaded and then reimport. This is to ensure Pester is testing the most up-to-date version. Otherwise, you might be changing the code inside the PSM1 file, but Pester will be looking at the module that's loaded in memory.
I name all my Pester tests files after the module name and end the file name with .Tests.ps1. So, for example, if I have a module called SQLServer, my test file will be SQLServer.Tests.ps1, and I will include it in the same folder as the module itself. This is good practice. Because I have a standard naming convention, I can figure out what the PSM1 file path is from within the test script itself. Below, I'm using the $MyInvocation automatic variable to figure out the name of the test script inside the script itself and inferring the module name.
$ThisModule = $MyInvocation.MyCommand.Path -replace '\.Tests\.ps1$' $ThisModuleName = $ThisModule | Split-Path -Leaf
Once I have the name of the module I'm testing, I can remove it by using Get-Module –Name $ThisModule –All and then piping the results to Remove-Module, because if you have more than one version installed, not using –All will leave lingering versions, and Pester will complain.
Get-Module -Name $ThisModuleName -All | Remove-Module -Force -ErrorAction Ignore
Once I know that I’ve removed all versions from the session, I'll run Import-Module and use the -Force switch just in case. Also, notice that I'm using an ErrorAction of Stop here. If there's a problem importing the module, I want to throw a terminating error immediately and not proceed with the tests.
Import-Module -Name "$ThisModule.psm1" -Force -ErrorAction Stop
Now Pester can read the functions I defined in the module, and I can build the tests accordingly.
But, as you might know, every function declared in the module isn't necessarily available by default. The Export-ModuleMember command and the FunctionsToExport key used in the manifest file can limit the functions that are exported. This means that some functions might be private. Pester can't get to these by default if you simply import the module. Instead, Pester has a keyword called InModuleScope that allows you to run Pester inside the module's scope to see even those private functions that aren't exported.
Perhaps I have a module called MyModule with a couple of functions called Do-Something and Set-Something.
A module called MyModule
Notice that both appear by default. Now I'll add Export-ModuleMember Do-Something to the bottom of the module, so only the Do-Something function is exported.
You can now see that only Do-Something shows up. When I try to run a Pester test against this function, it won't be able to find it.
Now, let's add the InModuleScope keyword to the test file.
$ThisModule = $MyInvocation.MyCommand.Path -replace '\.Tests\.ps1$' $ThisModuleName = $ThisModule | Split-Path -Leaf Get-Module -Name $ThisModuleName -All | Remove-Module -Force -ErrorAction Ignore Import-Module -Name "$ThisModule.psm1" -Force -ErrorAction Stop InModuleScope $ThisModuleName { describe 'Set-Something' { it 'does this thing' { Set-Something } } }
Yay! It passed. You can see that by using the InModuleScope keyword, Pester can even see functions that were not exported. This is just one benefit of using the keyword. The Pester wiki outlines the other uses.
Subscribe to 4sysops newsletter!
You've now seen how you can test modules with Pester with scripts. Now you have no excuse not to write Pesters for those modules as well! For a full breakdown on what's possible with the PowerShell testing framework Pester check out The Pester Book by myself and Don Jones.
Adam, thank you for your Pester articles!