DEV Community

Cover image for Redirect Out-File to TestDrive: in your PowerShell Pester test scripts with this one weird trick
Mark Wragg
Mark Wragg

Posted on

Redirect Out-File to TestDrive: in your PowerShell Pester test scripts with this one weird trick

I was writing some Pester unit tests recently for a PowerShell script that creates a modified version of a template file before it is then uploaded via an API and deleted. The script uses Out-File to output the updated file and because it only exists temporarily the location it writes to is hard coded in the script.

I wanted to write some tests that would validate the file was created and with the expected updated values. I could have just let the script write the file to it's default location and validate it there, but I'd have to mock Remove-Item to stop it deleting the file and then have my test script remove it afterwards. Another approach might have been to do some refactoring, have the output file and input parameter of the script, or break the script up into smaller functions, but I didn't particularly want to change the script.

Pester provides an automatic location TestDrive: that can be used to isolate file operations somewhere unique and that it automatically cleans up when the test completes, so all I needed to do was override the behaviour of Out-File to write to TestDrive:. You can do that with a Mock, and the one-weird-trick here is to store the Out-File command into a variable so that we can use it within the Mock, but change it's input parameters:

BeforeAll {
  $OutFile = Get-Command -Name 'Out-File'

  Mock Out-File -ParameterFilter { $FilePath -match 'file.txt' } -MockWith {
    $InputObject = $PesterBoundParameters['InputObject']
    $FilePath = Join-Path $TestDrive 'file.txt'
    & $OutFile -InputObject $InputObject -FilePath $FilePath
  }
}

It 'Should update the file with the expected values' {

  $FilePath = (Join-Path $TestDrive 'file.txt')

  $FilePath | Should -FileContentMatch 'expectedstring1'
  $FilePath | Should -FileContentMatch 'expectedstring2'
  $FilePath | Should -FileContentMatch 'expectedstring3'
}
Enter fullscreen mode Exit fullscreen mode

It is necessary to put the command into a variable because once a command is mocked in a test script, calling it would invoke the Mock rather than the command, so you'd end up in a recursive loop. Note how the Mock uses $PesterBoundParameters to access the parameters that were passed to the command in the script, but we then override what would have been provided for -FilePath with our TestDrive: pathed value.

My script still mocked Remove-Item btw, so we could check that it had been called the expected number of times. There was no need to let it actually do a file removal because the clean up of TestDrive: takes care of that for us. It's usually best to mock away the destructive behaviour of cmdlets when testing so that if the script has been badly modified in some way you don't risk invoking the actual behaviour for the purpose of running your tests.

Top comments (0)