In this article, we're going to cover how to use Pester to test a PowerShell module, and we'll review some of the module-specific features Pester provides that make thoroughly testing a PowerShell module a piece of cake.
Avatar

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:

The output

The 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

A module called MyModule

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.

Adding Export ModuleMember Do Something to the bottom of the module

Adding Export ModuleMember Do Something to the bottom of the module

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.

Only Do Something shows up

Only Do Something shows up

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
    }
  }
}
Adding the InModuleScope keyword to the test file

Adding the InModuleScope keyword to the test file

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.

1 Comment
  1. Avatar
    Dmitry 7 years ago

    Adam, thank you for your Pester articles!

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account