Mock Basics: Replacing a Command's Behavior
- powershell
- pester
- testing
- mocking
- unit-testing
In Part 17 we covered why you mock. Now we make it real. Pester's Mock command swaps a live command for a scriptblock you write, so you can return whatever data you like and never trigger the dangerous or slow behavior. By the end of this post you'll have a test that runs completely offline, no network or disk required.
What you'll learn
- The
Mock CommandName { ... }syntax - Where a
Mockmust live so it actually applies - Returning canned values from a mock
- Making a mock throw to simulate failure
- What a mock does by default when you give it no body
The basic shape
A mock has a name and a body. The name is the command you want to replace; the body is the scriptblock that runs instead.
Describe 'Mock demo' {
It 'returns the canned value instead of reading a real file' {
Mock Get-Content { 'fake file contents' }
Get-Content -Path 'C:\does\not\exist.txt' | Should -Be 'fake file contents'
}
}The file path does not exist, yet the test passes. Because Get-Content is mocked inside this It, the real command never runs — Pester intercepts the call and runs your scriptblock instead.
Where a Mock must live
This trips up nearly every beginner, so read it twice.
A Mock only applies inside the Pester block where it is defined (and any blocks nested within it). Define it inside an It and it covers that one test. Define it inside a Describe or Context — usually in a BeforeAll or BeforeEach — and it covers every test in that block.
Describe 'Scope of a mock' {
BeforeAll {
Mock Get-Date { [datetime]'2020-01-01' }
}
It 'sees the mock' {
(Get-Date).Year | Should -Be 2020
}
It 'also sees the mock' {
(Get-Date).Year | Should -Be 2020
}
}Two rules follow from this:
- The command must already be known when the mock is set. If your function lives in a
.ps1file, dot-source it before (or in the sameBeforeAllas) the mock — see Part 13. Mocking a command Pester has never heard of will error. - A
Mockat the very top of the file, outside any block, runs in the Discovery phase (Part 10) and will not behave as you expect. Keep mocks insideDescribe/Context/Itor their setup blocks.
A realistic example: mock the input, test the parser
Imagine a function that reads a config file and returns the value of one setting. The reading is the side effect; the parsing is the logic you care about.
function Get-ConfigValue {
param(
[string]$Path,
[string]$Key
)
$lines = Get-Content -Path $Path
foreach ($line in $lines) {
$parts = $line -split '=', 2
if ($parts[0].Trim() -eq $Key) {
return $parts[1].Trim()
}
}
return $null
}To test the parser without a real file on disk, mock Get-Content so it returns the lines you want:
Describe 'Get-ConfigValue' {
It 'returns the value for a known key' {
Mock Get-Content {
@(
'host = localhost'
'port = 5432'
'name = sales'
)
}
Get-ConfigValue -Path 'any.conf' -Key 'port' | Should -Be '5432'
}
It 'returns null for a missing key' {
Mock Get-Content { @('host = localhost') }
Get-ConfigValue -Path 'any.conf' -Key 'port' | Should -BeNullOrEmpty
}
}The path any.conf never has to exist. Each test feeds the parser exactly the data it needs to exercise one behavior. That is mocking earning its keep: the slow, fragile dependency is gone, and the logic is tested in isolation.
Making a mock throw
Real commands fail — the network drops, a file is locked. To test how your code handles that, make the mock throw:
Describe 'handles a read failure' {
It 'returns null when the file cannot be read' {
Mock Get-Content { throw 'Access denied' }
# Get-ConfigValue should be written to handle this gracefully;
# here we just show the mock throwing.
{ Get-Content -Path 'locked.conf' } | Should -Throw 'Access denied'
}
}This lets you test error-handling paths that are nearly impossible to trigger reliably with the real command.
The do-nothing default
If you give Mock an empty body, the command is replaced with a no-op: it runs, does nothing, and returns nothing.
Describe 'silence a side effect' {
It 'does not really delete anything' {
Mock Remove-Item { } # empty body = do nothing
Remove-Item -Path 'C:\important\file.txt' # safely no-ops
# No file was touched; the call simply did nothing.
$true | Should -BeTrue
}
}This is the safest default for destructive commands like Remove-Item or Stop-Service — you neutralize the side effect entirely while your function carries on as if it succeeded.
Try it yourself
Pick a function of your own that depends on an external command — a web call, a file read, a database query. Write a Describe with one It that mocks that command to return a fixed value, then assert your function produces the right result. Run it with your network cable unplugged (or Wi-Fi off) to prove it is truly offline.
Common mistakes
Defining the mock before the function is loaded. If you mock a command in a
BeforeAllthat runs before the dot-source, or you forget to dot-source at all, Pester cannot find the command and the mock errors. Load the code first.Mocking a name the code does not actually call. If your function calls
Invoke-RestMethodbut you mockInvoke-WebRequest, the real call still happens. Mock the exact command name in the source.Expecting a return you never specified. A mock with an empty body returns nothing. If your test needs a value back, put it in the scriptblock.
Recap
Mock CommandName { body } replaces a real command with a scriptblock you control. Keep mocks inside Describe/Context/It (or their setup blocks), after the command is loaded. Return canned values to feed your logic, throw to simulate failure, and use an empty body to safely no-op a side effect.
Next up: Part 19 — Proving Interactions with Should -Invoke, where mocking goes from "return fake data" to "prove the call actually happened."