Pester Testing Self Contained Scripts

Words: 1009

Time to read: ~ 5 minutes

Update:
    2019-02-22: Added a test for Jakub’s (don’t freak out, don’t freak out) comment.
    2019-06-13: Check out Jakub’s post on the topic – Such elegance http://jakubjares.com/2019/06/09/2019-07-testing-whole-scripts/

I’m going to (optimistically) say that you are all Pester testing your scripts.

It makes me feel better to believe that. If you aren’t, then you don’t even realise the amount of mental energy that you are spending trying to keep track of thoughts like this:

“oh can’t touch that in case that breaks that which would break this and feck up that…”

Once you start Pester-ing your tests, it gets easier since you know that any unwanted bugs introduced by your changes will be captured and raised up to you.

So you code along, happy in the knowledge that you can focus on the problem and free up some mental memory to work on the problem at hand.

The first few lines of a Pester “.tests.ps1” file…

…follow the same format:

This is known as “dot-sourcing” the file. 

This normally isn’t a problem since the agreed best practice way of writing functions is to have a single file that just defines the function (preferably in a module) e.g. Do-Something.ps1.
That way you can dot-source the function . .\Do-Something.ps1 and then you can call the function normally from the console e.g. Do-Something -ToThis 'SQL Server'.

Where things differ…

…could be when you try to accommodate different people and create a .ps1 file that both defines and calls a function. Self Contained scripts, if you would call them that.

Normally the reason that I’ve heard from this is you’re trying to help a non-technical minded person and they just want a file that they can open, hit “run”, and everything is done for them.

Have you ever tried to Pester test those files though? It’s not recommended, especially if your function removes or modifies objects.

It’s pretty hard to test something when it does the work when you try and load it in…

PSPowerHour

I was watching the video of PSPowerHour that was done on the 18th Feb 2019 and one of the videos was from Jakub Jares ( blog | twitter ) on testing self contained scripts.

If there is a video about Pester from Jakub Jares, then I recommend you watch it. Here’s a link to the part I was watching.

https://youtu.be/7uYDux0HJ7w?t=915

It’s impressive! Yet after the part about Aliases it got to a level that I just haven’t been able to reach yet. Plus I’m pretty sure that if I use Invoke-Expression in my scripts then Joel Sallow ( blog | twitter ) will have very strong words with me…

Whether for good or for bad, I have my own way of testing my self contained scripts.

I use the Abstract Syntax Tree (AST).

AST

Ah yes, the Abstract Syntax Tree! Honestly, I only know enough to know how to get what I want from it. It’s on my list of things to learn and get more comfortable with but then so is basically everything.

I’m still working on getting 30 hour days first 🙁

In case, you don’t know or want to figure out the AST then don’t worry! As most things the PowerShell community has you covered.

Mike Robbins ( blog | twitter ) has a great blog about getting into it and Chris Dent ( github ) has a function that does nearly all the work for you! Get-FunctionInfo

I saved the above into a file called Get-FunctionInfo.ps1, because consistency is important, and now I can use that to help solve our little “testing self contained scripts” problem.

Our Self Contained Script

Let’s say we have a self contained script like the one below, saved in the file Get-Name.ps1.

function Get-Name {
    [CmdletBinding()]
    param(
        [String]$Name = 'you'
    )

    'Hello, {0}' -f $Name
}

Get-Name -Name Shane
Runs

Now if we were to load Chris’ Get-FunctionInfo script, and run it against our function, we get…

# dot source the script to use it
. .\Get-FunctionInfo.ps1

Get-Function -Path .\Get-Name.ps1
Sparse

Don’t worry, there’s more information then that available. Let’s save the information in a variable and run Get-Member on it.

$fInfo = Get-FunctionInfo -Path .\Get-Name.ps1
$fInfo | Get-Member
So much more!

We take a quick look at the Definition property and we see something very familiar.

$fInfo.Definition
Our function!

So we have our function definition but how to add it to our scope? Personally I use the below

# I'm going to add a "2" after the name to show it's ours!
$FakeFunction = "function $($fInfo.Name)2 { $($fInfo.Definition) }
$OurFakeFunction = [scriptblock]::create($FakeFunction)

# Now we can dot-source our variable
. $OurFakeFunction
Get-Name2
Mwahaha!

Now our Get-Name2 runs and works just like our original script. Lets Pester test this now.

Now we can test it!

Let’s run a simple test in a brand new window to prove there’s no funny business.

describe 'testing our vGet-Name' {
     # Get our self containted script info
     . .\Get-FunctionInfo.ps1
     $fInfo = Get-FunctionInfo -Path .\Get-Name.ps1

     # Create something we can dot-source
     $Fake = "function $($fInfo.Name) { $($fInfo.Definition) }"
     $FakeFunc = [scriptblock]::Create($Fake)
     . $FakeFunc

     Context 'Results' {
         it 'should have the expected default result' {
             Get-Name | Should -Be 'Hello, you'
         }

         it 'should have the expected result for passed in values' {
             Get-Name -Name 'Shane' | Should -Be 'Hello, Shane'         
         }
    }
}

UPDATE: 2019-02-22.

Jakub added a comment to this post saying that 1). he’s going to work his presentation into a module, and 2). …


I like your approach with ast as well, but I think it won’t work when you use $PSScriptRoot in your script.

Jakub Jares

So in the interest of completeness, I wanted to test it out and add it here. First of all, we have to add the $PSScriptRoot to our script.

I’ve gone back to gists after seeing the default code formatter and having the automatic response of “eww”. 😐

Running this shows that the $PSScriptRoot variable works.

I already knew it was there…I typed it in…

Now let’s see what happens when we try to use our testing workaround with this function…

I call that a whole load of “nope”

And after that test, we’ve proved that Jakub was right!

Did you expect otherwise?

Now if you’ll excuse me…

… apparently I have to go learn a lot more about Scopes and Session Contexts and then re-watch Jakub’s video!

Author: Shane O'Neill

DBA, T-SQL and PowerShell admirer, Food, Coffee, Whiskey (not necessarily in that order)...

7 thoughts on “Pester Testing Self Contained Scripts”

  1. param([switch]$Testing)

    function Get-Name {
    [CmdletBinding()]
    param(
    [String]$Name = ‘you’
    )

    ‘Hello, {0}’ -f $Name
    }

    if(!$Testing){Get-Name -Name Shane}

  2. Thanks for the article 🙂 That invoke expression is just for the demo, in real life you’d use just an empty function. I’ll clean it up and publish it as a module. And add it to Pester v5. It already does a similar thing with Add-Dependency.

    I like your approach with ast as well, but I think it won’t work when you use $PSScriptRoot in your script.

Leave a ReplyCancel reply

Discover more from No Column Name

Subscribe now to keep reading and get access to the full archive.

Continue reading

Exit mobile version
%%footer%%