Writing unit tests for PowerShell code is a great way to make sure that the logic flow within a function (or just a script) is working as intended. Originally, I struggled a bit with this idea and wanted to make every test a functional test, but finally set aside a bit of time to explore the possibilities with unit testing. What’s the difference? A unit test is focused on testing a bit of code with supplied (mocked) inputs, while functional testing is generally used against a live system to see how the code works in reality.
While both types of tests can be helpful, I’ve come to realize that functional testing can be overkill – or sometimes even a luxury – in many cases. There are plenty of times where I just want to make sure that my inner logic is being respected. A unit test is great for that! I can supply mock values to the test, see if the logic handles those values correctly, and determine if all sorts of use cases are being handled appropriately.
In this post, I’m going to cover how to perform unit testing with PowerShell’s Pester module using the Mock command.
The Mock Command
Pester is a wonderful framework used by PowerShell to perform tests. It’s at the heart of Vester, another project in which I’m involved. Within the framework exists a Mock command, which can be used to override the typical functionality of any command used for a test. It’s basically like saying “when I run this test code, I want to pre-determine the results of a command with a set of mock results.” The scope of mock extends to the Describe block within a Pester test.
I use Mock to fake the results from an API call. To demonstrate this, let’s look at the Connect-Rubrik.Tests.ps1 unit test contained within the Rubrik PowerShell module.
Describe -Name 'Connect-Rubrik Tests' -Fixture { It -name '[v1] Valid credentials' -test { # Arrange Mock -CommandName Invoke-WebRequest -Verifiable -MockWith { return @{ Content = $resources.v1.SuccessMock StatusCode = $resources.v1.SuccessCode } } ` -ParameterFilter { $uri -match $resource.v1.URI } -ModuleName Rubrik # Act Connect-Rubrik -Server '1.2.3.4' -Username test -Password ('test' | ConvertTo-SecureString -AsPlainText -Force) $rubrikConnection.token | Should Be (ConvertFrom-Json -InputObject $resources.v1.SuccessMock).token # Assert Assert-VerifiableMocks }
Let’s break down what Pester is doing and chunk up the code a little.
Building the Describe Block
Describe -Name 'Connect-Rubrik Tests' -Fixture { It -name '[v1] Valid credentials' -test {
- We start by building the
Describe
block, which is a logical container for Pester tests. In this case, I’m testing theConnect-Rubrik
function. - We then define the first test, named “[v1] Valid Credentials,” in which I’m going to pass along valid credentials to the function.
The “Arrange” Section
# Arrange Mock -CommandName Invoke-WebRequest -Verifiable -MockWith { return @{ Content = $resources.v1.SuccessMock StatusCode = $resources.v1.SuccessCode } } ` -ParameterFilter { $uri -match $resource.v1.URI } -ModuleName Rubrik
- The Mock portion is used to alter the results for whenever my script decides to use
Invoke-WebRequest
. Rather than allowing the command to really reach out to the API, the Mock command skips that and automatically returns whatever is in thereturn
section. In this case, usingInvoke-WebRequest
will result in getting a hashtable containing the SuccessMock and SuccessCode values from my $resources object. - ParameterFilter is a way to control when the mocked command runs. In this case, I’m stating that Mock should only alter the return data when the URI being called has a soft match to the URI contained within my $resource object. Otherwise, Mock won’t touch the
Invoke-WebRequest
command. It’s also a way to make sure that the code is being called because Line 6 has the-Verifiable
parameter (which is enforced later). - ModuleName is a way to tell the Mock command to insert itself into the Rubrik module. Otherwise, the test wouldn’t run properly.
The “Act” Section
# Act Connect-Rubrik -Server '1.2.3.4' -Username test -Password ('test' | ConvertTo-SecureString -AsPlainText -Force) $rubrikConnection.token | Should Be (ConvertFrom-Json -InputObject $resources.v1.SuccessMock).token
- I’m now testing the
Connect-Rubrik
code and sending it some arbitrary values (such as an IP address of 1.2.3.4) and fake credentials. - Because we’re mocking
Invoke-WebRequest
there is no need to supply any real IP or credentials to the API; the return value will always be what was instructed in the previous section using Mock. - I then make sure that the mocked token that I gave the function has been loaded properly.
The “Assert” Section
# Assert Assert-VerifiableMocks
- The use of
Assert-VerifiableMocks
will make sure that any Mock with the-Verifiable
parameter flagged has been run. - If not, the test fails stating that my mocked test code never actually ran.
Results
Here’s a screenshot showing the results of the five tests – one to load the $resources object and four to test valid and invalid credential handling to make sure that any logic found within the function is written correctly.
The folks at Pester also have a short write-up on how to use Mock. Once I realized that Mock was actually used to override the return values of any command – such as Invoke-WebRequest
– it made a lot of sense to start using the Mock command. I still use functional testing for bigger releases, but it’s nice to have all of the unit testing automated for making sure that logic flows are being respected as I hoped they would. Enjoy!