I used a long time before looking into Pester to test my code. Or, worded differently, for the longest time I was not testing my code. But as soon as I started creating PowerShell modules that was more than just small time projects, I had to step up the production quality. As soon as I had written some tests, I wanted to have those tests run every time I did a pull request. This helps me catch bugs before publishing the new version of my module, and saves me from a ton of stress.

This is not an explanation on how Pester works. If you want to learn how to write tests for PowerShell you can either check out pester.dev, or this book by Adam Bertram.

A basic example

If we look at the bare essentials that you need to create a GitHub action that runs pester, we would have something like this…

name: Pester
on:
  push:
    branches: [ main ]
jobs:
  test-pwsh:
    runs-on: windows-latest
    steps:
    - uses: actions/checkout@v2
    - name: Run Pester tests
      run: |
        Set-PSRepository psgallery -InstallationPolicy trusted
        Install-Module -Name Pester -RequiredVersion 5.0.4 -Confirm:$false -Force
        Invoke-Pester -Path "tests"        
      shell: pwsh

Just to break it down, here we have an action that:

  • Runs on every git push to a branch called main (change this to suite your needs)
  • Runs a job called test-pwsh on the latest available Windows image
  • Checks-out the repository to our workspace, so we can interact with our code
  • Run a couple of cmdlets inline in PowerShell 7, as defined by the shell selection at the end
    • Set the PSGallery as a trusted source
    • Install Pester, make sure nothing stops it from doing so by using -Confirm:$false and -force.
    • Run Pester on the directory containing the tests files, in my case it is called tests. Imagination is my string suite.

Some of the parameters and switches are probably unnecessary, as the container you get served is completely clean. I just like the false sense of security by defining -Force 😅

Turns out, this works just as you expect it to. However, one flaw here is that we are now running this on PowerShell 7 only, Windows only. If you are creating a module that you want to be usable for anyone running PowerShell, you need to run your tests on all platforms available, as well as Windows PowerShell. Turns out, GitHub Actions has a solution for that.

Running same tests on multiple platforms

name: Pester
on:
  pull_request:
    branches: [ main ]
jobs:
  test-pwsh:
    strategy:
      matrix:
        platform: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.platform }}
    steps:
    - uses: actions/checkout@v2
    - name: Run Pester tests (pwsh)
      run: |
        Write-host $PSVersionTable.PSVersion.Major $PSVersionTable.PSRemotingProtocolVersion.Minor
        Set-PSRepository psgallery -InstallationPolicy trusted
        Install-Module -Name Pester -RequiredVersion 5.0.4 -confirm:$false -Force
        Invoke-Pester -Path "tests"        
      shell: pwsh
  
  test-posh:
    runs-on: windows-latest
    steps:
    - uses: actions/checkout@v2
    - name: Run Pester tests (PowerShell)
      run: |
        Write-host $PSVersionTable.PSVersion.Major $PSVersionTable.PSRemotingProtocolVersion.Minor
        Set-PSRepository psgallery -InstallationPolicy trusted
        Install-Module -Name Pester -RequiredVersion 5.0.4 -Confirm:$false -Force
        Invoke-Pester -Path "tests"
        if ($Error[0].Fullyqualifiederrorid -eq 'PesterAssertionFailed') {exit 1}        
      shell: powershell

In this example, we run two jobs. One called test-pwsh and one called test-posh. The first one is using the strategy called matrix to run the job for each of the platforms we have defined. This way, we save us from manually creating three jobs that run the same code.

We then have to create a new job to run on a different shell. Turns out even though pester fails in PowerShell 5.x the job still gets marked as a success, so I added a little if statement to make sure that it runs exit 1.

Overall, that’s it for this topic. Obviously, we can do a real deep dive here but for now I just want to make sure that more people are testing their code. Questions, comments or feedback? Feel free to reach out to me on twitter!