When writing Pester infrastructure tests, you'll ultimately run across a common problem; dealing with dependencies. Unlike unit tests, infrastructure tests actually run real code and touch the real environment. A real environment is complicated and full of dependencies on compute, networking, storage, etc.

To build a reliable and accurate infrastructure test, it's important to be cognizant of these dependencies and accounts for them in your tests. In this article, we're going to cover two ways to do just that.

Test Dependencies

First of all, what a "dependency" anyway? A dependency can mean anything from:

  • a VM being provisioned
  • a folder that must be created before a file can be placed inside
  • a domain being available before adding an Active Directory user to it
  • ..and more

Everything has some kind of dependency. Thinking of dependencies in this way will make your head spin especially if thought of more granularly like what it takes to render a web page! This means you're going to have to make some tradeoffs.

An Example Walkthrough

Let's start with a simple example. I have some code that creates an Active Directory user. What must be in place before this task can be tested?

Thinking from the closest dependency to the farthest way, we might need:

  • an organizational unit to place the user in
  • a domain to put that OU in
  • at least one server must be running Active Directory Domain Services (ADDS)
  • a VM or physical server is setup for the DC
  • ...and on and on

Infrastructure dependencies can run deep, but at some point, you must assume some things.

In the upcoming example, I'm going to assume that I have a network in place and a server already stood up that's supposed to be a domain controller. Other than that, I'll be testing all other dependencies. In this case, I'll be checking to ensure that server is actually a domain controller and an OU is setup that we'll be placing the AD user in.

To get started, you'll need to have Pester installed (Install-Module Pester) on a machine and added as a member of the domain that the user would be created in. Also, in order for the test to run all the way through, humor me and first ensure that the domain controller (DC) is online. This will prevent us from having to bring up a DC for every test run.

For this article, we're going to be testing infrastructure dependencies at the Pester describe block level. This means that for any tests to run, the environment must be in a preferred state.

To do this, I'll first define all of the variables to use in these dependency tests. For our example, I'll need the name of the domain controller and the name of the OU.

describe 'New-CompanyADUser' {
    $domainController = 'TESTDC'
    $ouPath = 'OU=CompanyUsers,DC=mylab,DC=local'
}

I'll create a file called New-CompanyADUser.Tests.ps1 and create a describe block that looks like the above example.

Next, I'll add the prerequisite checks. To be able to add and remove multiple checks quickly, I'll create an array and define each test as a scriptblock.

$conditions = @(
    @{Name = 'DC is online'; Action = { Test-Connection -ComputerName$domainController -Quiet -Count 1 }}
    @{Name = 'OU exists'; Action = { Invoke-Command -ComputerName$domainController -ScriptBlock { Get-AdOrganizationalUnit -Filter {Name -eq $using:ouPath }}}}
)

I can now run each of these checks by iterating over each with a foreach loop. Below I'm checking to see if each of the checks returns anything. If they return nothing or False, the check will fail, and the test will fail before it even gets started.

@($conditions).foreach({
    if (-not (& $_.Action)) {
        throw "The dependency test [$($_.Name)] failed. Cancelling further tests."
    }
})

I'll now run the test while my required OU is not present.

PS> Invoke-Pester -Path C:\New-CompanyADUser.Tests.ps1

You can see below that I received an important message about what dependency test failed and that a test failed.

I'll now create the required OU and see what happens.

This time no error was thrown above, and no tests failed. If I had actual tests in this example, it would have shown pass/fail instances for each it block.

Wrap up

This was a simple example of infrastructure dependency checking with Pester.

If necessary, this concept can be taken to the next level by creating dependencies on the fly. Or, if more granular checking is needed, the checks could be incorporated into the it blocks themselves by using the Set-TestInconclusive Pester command as well. This would ensure that if a dependency check failed, the test would not be labeled a pass/fail but be represented in an inconclusive state.

Join the Jar Tippers on Patreon

It takes a lot of time to write detailed blog posts like this one. In a single-income family, this blog is one way I depend on to keep the lights on. I'd be eternally grateful if you could become a Patreon patron today!

Become a Patron!