Unit testing in PowerShell, introduction to Pester
Tags: powershell, pester, testing, cicd
Published: 2019-07-02 07:00:04

---
title: Unit testing in PowerShell, introduction to Pester
published: true
description: How can you perform unit and acceptance testing in PowerShell
tags: PowerShell, Pester, testing, cicd
---

When people (at least IT people) thinks about PowerShell, they think about some magic commands and maybe about some scripts and sometime more. 
PowerShell is more than that. It’s not only a sequential programming language it’s also a functional language. You can create functions and modules, but not only you can use it as configuration management with DSC and other tools, you can use it in serverless platforms like AWS Lambda or Azure Functions, you can build cloud solutions either on Azure or AWS. You can build complete solutions with PowerShell. 

Even if PowerShell is seen as an Ops tools it follows the same methods and patterns than any other programming language. 
Unit testing is one of these patterns. It ensures that section by section codes work as expected. 

PowerShell own a Unit testing framework. Its name is [Pester]( https://github.com/pester/Pester), it’s the ubiquitous test and mock framework for PowerShell. It’s a Domain Definition Language and a set of tools to run unit and acceptance test. 


## Installing Pester 

Even if Pester is now installed by default on Windows 10, it’s better to update it to the latest version (now in 4.8.x, soon in 5.x).
Pester can be installed on Windows PowerShell (even on old version) and on PowerShell Core


```powershell
Install-module -name Pester 
```

You may need to use the force parameter if another is already installed 

```powershell
Install-module -name Pester -force
```

Yes, you don’t need to use -SkipPublisherChek anymore, the Pester module is signed now.

## The basic 

A test script starts with a Describe. Describe block create the test container where you can put data and script to perform your tests. Every variable created inside a describe block is deleted at the end of execution of the block.

```powershell
Describe {
   # Test Code here
}
```
you can add tags and a name to each describe block to define testing scenario 

```powershell
Describe -tag "SQL" -name "test1" {
 # Test Code here
}

Describe -tag "SQL" -name "test2" {
 # Test Code here
}
```
Tests inside a describe block can be grouped into a Context.
Context is also a container and every data or variable created inside a context block is deleted at the end of the execution of the context block
Context and Describe define a scope.

```powershell
Describe -tag "SQL" -name "Sqk2017" {
    # Scope Describe
    Context "Context 1" {
        # Test code Here
        # Scope describe 1
    }

    Context "Context 2" {
        # Test code Here
        # Scope describe 2
    }
 
}
```

## It Block 

To create a test with Pester we simply use the keyword It. The It blocks contain the test script. This script should throw an exception. 
It blocks need to have an explicit description It "return the name of something" and a script block. The description must be unique in the scope (Describe or Context).
It description can be static or dynamically created (ie: you can use a variable as description as long as the description is unique)


The command Should let you to compare objects, something from your code against something expected, and throw an error if the It bloc fail. 
Should introduce an assertion, something that must be true and if not throw an  error. 

There are several assertions:


| ASSERTIONS   |      DESCRIPTION      | 
|----------|:-------------:| 
| Be |  Compare the 2 objects|
| BeExactly |    Compare the 2 objects in case sensitive mode   |
| BeGreaterThan | The object must be greater than the value |
| BeGreaterOrEqual | The object must be greater or equal than the value |
| BeIn | test if the object is in array |
| BeLessThan | The object must be less than the value |
| BeLessOrEqual | The object must be less or equal than the value |
| BeLike | Perform a -li comparaison |
| BeLikeExactly | Perform a case sensitive -li comparaison  |
| BeOfType | Test the type of the value like the -is operator  |
| BeTrue | Check if the value is true |
| BeFalse | Check if the value is false |
| HaveCount | The array/collection must have the specified ammount of vallue  |
| Contain | The array/collection must contain the value, like the -contains operator  |
| Exist | test if the object exist in a psprovider (file, registry, ...)  |
| FileContentMatch | Regex comparaison in a text file |
| FileContentMatchExactly | Case sensitive regex comparaison in a text file |
| FileContentMatchMultiline | Regex comparaison in a multiline text file |
| Match | RegEx Comparaison |
| MatchExactly | Case sensitive RegEx Comparaison |
| Throw | Check if the ScriptBlock throw an error  |
| BeNullOrEmpty | Checks if the values is null or an empty string |


```powershell
Describe "test" {
    It "true is not false" {
        $true | Should -Be $true
    }
}
```

As you can see the It block is divided into two parts. The first one, the name, identify the test. The second one, the Script bock can contain any code you need as long as you pass the result to a Should statement by Pipeline. The should Statement will evaluate the data and throw an error if the condition of the statement aren’t meet.
We can also build negative assertion with the -not keyword

```powershell
Describe "test" {
    It "true is never false" {
        $true | Should -not -Be $false
    }
}
```

When I write unit tests, often I need to test the same portion of code or function several times with different conditions or different data. As I am lazy, I don’t like to write the same test more than one or maybe two times. 
For that I just need a dictionary object and -testcase

```powershell
$BufferSize = 4
$DataTypeName = [System.Byte[]]::new($BufferSize)
$RandomSeed = [System.Random]::new()
$RandomSeed.NextBytes($DataTypeName)

$TestData =  @(@{"TestValue" = "Value One"; "TestType" = "String"},@{"TestValue" = 2; "TestType" = "int32"},@{"TestValue" = $DataTypeName; "TestType" = "Byte[]"}) 

function get-DataTypeName {
    [CmdletBinding()]
    param (
        $value
    )
    return ($value.getType()).name
}



Describe "test some values" {
    It "Test if <TestValue> is a <TestType> Object"  -TestCase $TestData {
        param($TestValue, $TestType)
        get-DataTypeName -value $TestValue | Should -Be $TestType
    }
}

```

When you use -TestCase with a dictionary object, the It block will iterate the object and perform a test for each iteration. 
To display the value in the It description the <> is needed. Inside the It bloc; you need to pass the data as parameters and get their value in parameters variables


## On how to write and run Pester tests

You can virtually run pester in any PowerShell file but by convention, it’s recommended to use a file named xxx.tests.ps1. One per PowerShell file in your solution. If you have one file per function it mean one pester .tests.ps1.
You can place pester files in the same folder as your source code or you can use a tests folder at the root of the repository. 
Personally, I prefer to use a tests folder but wherever the test files are, they need to load the code. 


To do that you can dot source the script you need to test. 

To run all the tests in the .tests.ps1 files,  you just need to use invoke-pester from the tests folder. 
If the It and Describe blocs are the heart of the testing process, invoke-pester can be the liver as it can filter tests and transform the result. 

By using -Testname or/and -Tag parameter in conjunction with -passThru you can filter describe bloc with the name of the test or the tags. 

The -Strict param allow to consider any pending or skyped test will create a fail.

By default, pester produce the result directly as a text in the console. In some situation you may want to use another format. 

The -PassThru parameter let you produce a custom PowerShell object instead of standard output. It produces a PSCustomObject with the number of tests, the number of passed, skipped, pending or failed. It contains also an array, TestResult with all the test result. 

The -EnableExist switch will make invoke-pester exit with an exit code. This code is 0 if all tests pass or the number of failled test. This can be usefull is some continuous integration scenarios.

OutputFile and OutputFormat parameters will redirect the output to a file in NUnit xml format. By default, Pester 4.x will output the result using NUnit format using 2.5 Schema. You cannot change the format event with -OutputFormat (there is only one possible value NUnitXml). Using the Output can be useful with some CI tool which display Nunit test result


## Code Coverage

Code coverage measure the degree to which your code is executed by your tests. The measure is expressed in a percentage. 
The coverage percentage don't mean that the module or the script is bug free, it only mean that the code has been run by during the test process. 

To generate code coverage metric with Pester you need to use invoke-pester with -coverage following the path of the script you need to test. 

```powershell
    invoke-pester -script .\function.test.ps1 -coverage .\function.ps1
```

You can use wildcard and a coma to analyze multiple files

```powershell
    invoke-pester -script .\functions.test.ps1 -coverage .\function.ps1, .\*.psm1
```

You can also use a hashtable for coverage. It must contain the Path key. You can use the Function key to limit the analyze to these functions or you can use StartLine or EndLine to limit the analyze. 

Note that having a 100% coverage do not implies that the code is bug free, it is just an indication that all your code has been executed during the test


## Advanced usage

## Test Drive 

A function may need to manipulate the file system, it can create, delete or modify one or more files. It isn't desirable to change file system during the test phase. 
Pester provide a drive named TestDrive:. The drive is available in the scope of the test (Describe or Context) and automatically deleted at the end of the scope.
When using the test drive, Pester create a random folder in $env:tmp and use it to put file from the current test drive in the scope. 
You can use testdrive:\ or the $testdrive variable to perform any file operation during the test.

```powershell
Describe "test" {

    new-item (Join-Path $TestDrive 'File.txt') 

    It "Test if File.txt exist" {
       (test-path -path (Join-Path $TestDrive 'File.txt')  ) | Should -Be $true
    }
}
```

## Mocking 

Scripts and functions rely on modules and function that may not be present on the test machine, or perform a destructive operation. 
You design a function that create or delete an active directory account.
you may not have any active directory module on the test computer and/or you do not want to make any change on production computer
You create a module that change the configuration of the server and you don't want to change anything during the test phase. 
You just need to be sure that the code is correct.

Mocking in Pester, let you imitate the result of a function or command called during the test. When you use Mock, you simply create the result for a given command so you can test other part of your script. 


To mock a function or a command you simply need to use Mock and the name of the function or the command. 


```powershell
Describe "test" {

    Mock remove-something { }

    It "Test if remove-something return null" {
       remove-something | Should  BeNullOrEmpty 
    }
}
```

Mocking is limited by the scope of the test, either Describe or Context. If you need to test different behavior you can use -ParameterFilter to create different result

```powershell
Describe "test" {

    Mock Get-AswerAboutLifeUniverseEverything { return 42 }  -ParameterFilter { $Name -eq "42" }
    Mock Get-AswerAboutLifeUniverseEverything { return 42 }  -ParameterFilter { $Name -eq "57" }
    Mock Get-AswerAboutLifeUniverseEverything { return 42 }

    It "Test if Get-AswerAboutLifeUniverseEverything return 42" {
       Get-AswerAboutLifeUniverseEverything | Should  be 42 
    }
    It "Test if Get-AswerAboutLifeUniverseEverything -Name 42 return 42" {
       Get-AswerAboutLifeUniverseEverything -Name 42 | Should  be 42 
    }
    It "Test if Get-AswerAboutLifeUniverseEverything -Name 57 return 42" {
       Get-AswerAboutLifeUniverseEverything -Name 57 | Should  be 42 
    }
}
```

You can use Mock inside an IT block, in this case Mock will be available in the parent scope.

You may want to test if a mocked command was called during the test phase. It's possible to test this in 2 ways

Assert-VerifiableMocks, throw an error if a Mock command marked as -verifiable 


```powershell
Describe "test" {

    Mock Get-AswerAboutLifeUniverseEverything { return 42 }  -Verifiable 


    It "Test if remove-something return 42" {
       Get-AswerAboutLifeUniverseEverything | Should  be 42 
    }

    Assert-VerifiableMocks

}
```

Another way to test the execution of a mocked command is to use Assert-MockCalled. 
Assert-MockCalled must be placed inside an IT block. You need to use it with the -CommandName parameter. The Parameter take the name of the mocked command


```powershell
Describe "test" {

    Mock Get-AswerAboutLifeUniverseEverything { return 42 } 

    It "Test if remove-something return 42" {
       Get-AswerAboutLifeUniverseEverything | Should  be 42 
    }

    It "Test if remove-something was called" {
       Assert-MockCalled -CommandName Get-AswerAboutLifeUniverseEverything
    }
}
```

You can control how many times the mocked command was called with -times parameter. The Times X parameter test if the mocked command was called at least X times. 
If you need to test that the mocked command is called only X times you can use -Exactly

When mocking a function or a cmdlet, you may need to return an object. For example Get-AdUser return a Microsoft.ActiveDirectory.Management.ADUser object. Most of the time you can use a PScustom object to mock the returned object 


```powershell
    Describe "test" {

        Mock Get-AdUser { 
        
        $AObject = [PSCustomObject]@{
        Surname   	 	= "Marc"
        UserPrincipalName 	= "marc@mydomain.com"
        Enabled           	= $true
        SamAccountName    	= "marc"
        ObjectClass       	= "user"
        }
        Return $AdObject
        }  -ParameterFilter { $Identity -eq "Marc" }


        It "Test if the User is Valid" {
        (Get-AdUser -identity “Marc”).Enabled | Should  betrue 
        }

        It " Test if the User UPN " {
        (Get-AdUser -identity “Marc”).UserPrincipalName | Should  be “marc@mydomain.com”
        }
    }
```

But sometime a PsCustomObject is not enough. This is the case when you use class in your script. 
You can use New-MockObject to return any type of object. New-MockObject fake any type of object without the need to create it. You can use it to mock any .Net Type or you own class-based type

```powershell
Mock Get-Something {
	New-MockObject  -type 'System.Diagnostics.Process'
}
```

In some situation, you may need to execute some code before of after the tests. It could be variable initialization or environment setup. 

For example, you can create some files inside a test drive, open a connection to webservice, create random data. 
BeforeAll and AfterAll must reside inside a Describe or Context Block. It can be placed anywhere inside the block but it's better to place BeforeAll at the beginning and after all at the end.

BeforeEach and AfterEach run on each IT Block in the same way as BeforeAll/AfterAll


## Module Testing 

Pester can be used to test PowerShell module. But remember, pester need to access to the code to test code.   
First step, as module are loaded in memory you need to instruct PowerShell to remove the module. If not, you may not test the actual version of your code.

```powershell
Get-module -name ‘TheModule’ -all | remove-module -force -erroraction SilentContinue 
```

You can import the module 

```powershell
Import-Module -name PathTo\TheModule.psd1 -force -ErrorAction Stop
```
Problems, doing so you will only have access to public function declared inside FunctionToExport.
To avoid that, you need to use InModuleScope 

```powershell
InModuleScope TheModule {
	Describe 'Module Test' {
		It 'test one not exported function' {
			Get-NotExportFunction | Should not Throw 
		}
	}
}
```

This was my introduction to Pester. Tell me what you thinks