PowerShell is a very powerful language and framework. It’s not just for “advanced batch files”. When programming in PowerShell, you are encouraged to write modular code that can be combined in different ways. Your PowerShell code deserves the same quality testing that other languages provide.
If you look at most unit testing frameworks, they all provide a core set of features for writing test cases:
- A way to describe correct behavior. Typically this is done through a set of assertions. A test script is written with the “3 As”: Arrange, Act, and Assert. A good assertion library makes the intent of the test easy to code and understand.
- A way to run one or more tests. A test runner will locate and run tests, and report on the success or failure of the tests.
- A way to mock parts of the system that are not part of the system under test.
Depending on the framework you choose, you may need to combine two or more frameworks to get all of the pieces that you need.
PSST contains three tools to let you write your unit tests for PowerShell:
- PShould – a fluent assertion module
- PSMock – a mocking module
- PSate – a test runner module
Let’s see how we can start writing PowerShell tests with PSST. Assume we have a calculator class that we want to test:
function Add-Numbers($a, $b) {
if ($a -eq $null) { throw '$a cannot be null' }
if ($b -eq $null) { throw '$b cannot be null' }
return $a + $b
}
The first thing you would do is to write some assertions. Let’s import PShould and write a few cases:
Import-Module PShould
Add-Numbers 1 2 | Should Be 3
Add-Numbers 0 0 | Should Be 0
PShould supports lots of tests like “Should Contain” “Should Exist” and “Should Throw”. Let’s check for some throws:
{ Add-Numbers $null 1 } | Should Throw "cannot be null"
{ Add-Numbers 1 } | Should Throw "cannot be null"
Great. Now we have some tests that check the behavior of our calculator. But what if our code does something more complicated, such as opening a file or accepting input? Let’s try that with a calculator that takes input:
function Get-Number {
param ([int] $which)
Write-Host "Enter number ${which}:"
[int] $i = Read-Host
$i
}
function Do-Calculator {
return Add-Numbers (Get-Number 1) (Get-Number 2)
}
Do-Calculator
We want to test Do-Calculator, but it takes inputs from the user, so we can’t run an automated test case on it without doing some more work. Here we want to mock the Get-Number method so that we control the results. We will use PSMock to mock the method.
Import-Module PSMock
Enable-Mock | iex
Mock Get-Number { 1 }
Do-Calculator | Should Be 2
PSMock automatically creates a function that matches the one you are mocking, and lets you quickly override the function. It even lets you do conditional mocks:
Mock Get-Number { 1 } -when { $which -eq 1 }
Mock Get-Number { 2 } -when { $which -eq 2 }
Do-Calculator | Should Be 3
You could probably do this simply by writing your own functions, but PSMock lets you dynamically add and remove mocks, restoring the original methods. PSMock also lets you mock commandlets such as Get-ChildItem.
MockContext {
Mock Get-ChildItem { "nothing" }
Get-ChildItem c:\ | Should Be "nothing"
}
PSMock also provides a MockContext function, which automatically cleans up any outstanding mocks. This is very important if you are mocking any system commandlets (or you won’t be able to do a dir anymore!)
The next step is to organize our test cases with PSate:
Describing "Calculator" {
Given "two numbers" {
TestSetup {
Mock Get-Number { 1 } -when { $which -eq 1 }
Mock Get-Number { 2 } -when { $which -eq 2 }
}
It "adds them" {
Do-Calculator | Should Be 3
}
}
Given "a null number" {
TestSetup {
Mock Get-Number { $null } -when { $which -eq 1 }
Mock Get-Number { 2 } -when { $which -eq 2 }
}
It "throws" {
{ Do-Calculator } | Should Throw
}
}
}
Here we have organized our tests. The Describe block tells us what we are testing. The Given block describes a test scenario, which the TestSetup sets up. The It block asserts our tests for us. PSate even gives us pretty outputs:
Describing Calculator
Given two numbers
[+] It adds them [3 ms]
Given a null number
[+] It throws [3 ms]
We can even integrate PSate into our automated build process with Jenkins, TFS, or other tools.
Get started with PSST by visiting the project pages for documentation and installation instructions:
- PShould – a fluent assertion module
- PSMock – a mocking module
- PSate – a test runner module