With Pester it is really easy to test a single PowerShell function or a library of them, but what if your script contains some code that runs those functions?
Let’s say we have a script like this one:
# file GetSomethingScript.ps1
This script defines a function and then calls it. This is pretty normal approach if you want your script to actually do something, instead of being just a library of functions.
But see what happens if we decide to test this script using Pester:
And run the test code:
As you can see the
Get-Something function was run before running the tests as well. This is because we are dot-sourcing the tested script which means running it in the current scope (Using Dot Source Notation with Scope.). This is not good because the script should not run at this time.
So we need a way to dot source the script without running the
Get-Something function. To do that we check the value of
$MyInvocation.InvocationName as such:
That condition makes sure the script imports properly when dot sourced, and that it runs properly when invoked by the user from PowerShell console or PowerShell ISE.
Now you might be tempted to check if that worked with tests that go like this:
And on the first look it might seem to work correctly, but the second test is not proving anything. Dot sourcing the script inside the
It block redefines the
Get-Something function which makes it invisible for Mocking. So even if the function was called hundred times you’d still get green result. Checking for 0 calls with Mocking is always tricky.
Let’s prove that I am right by calling the
Get-Something function before
Assert-MockCalled which should make the
It fail. We also save the result, which should be empty as defined by the Mock, and check that as well.
This puts us in a weird situation, we have code in our script that we are unable to test. Unfortunately there is no simple solution for this at the moment.
Another problem you might face is that you want to check if the entry-point function of your script is run, but you don’t want to run the whole script to do that. The reason might simply be that the script takes too long to run.
Your knee-jerk reaction to this might be writing a test like this one:
But unfortunately that won’t work because invoking the script with & starts it in another scope where, Mocking can’t see it.
To run it in the current scope you could use dot sourcing, but as we learned earlier dot sourcing the script inside the It block redefines the Get-Something function which makes it invisible for Mocking. Not to mention you now have to find a different mechanism to solve the first (dot sourcing the script to Pester) problem.
Unfortunately there is no elegant way around it at the moment so the low-tech and frankly horrible solution I use is regexing the last lines of the script:
It gets job done, and actually works pretty well, but a better solution is definitely needed.
There is also a different approach to the whole problem, that Dave Wyatt suggested when I talked with him about the dot sourcing problem few months ago: Provide a parameter that is used only by Pester.
I don’t like it much because I believe the script should not expose anything test framework specific. But I will use that approach to show what is needed to actually test all those cases properly.
First here is a basic check if the script is being dot sourced, we just add and use the $UnderTest parameter instead of $MyInvocation.InvocationName:
This again gives you the power to stop the Get-Something from running when dot sourced, but still you can’t test that. And you can’t test if main entry point is run (when the script is invoked normally) either.
What would be actually needed is something like this:
But of course we can’t require Pester users to put all this logic in their scripts. Another way to do this would be to AST the script, make a backup copy of it and inject those definitions in, but I am reluctant to edit the tested script in any way.
$MyInvocation.InvocationName works flawelessly in ISE and Console, but it does not work in PowerGUI because it dot sources all scripts when running them, so as a result you won’t be able to run your scripts in PowerGUI.