Skip to content
6 min read

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 Mock must 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:

  1. The command must already be known when the mock is set. If your function lives in a .ps1 file, dot-source it before (or in the same BeforeAll as) the mock — see Part 13. Mocking a command Pester has never heard of will error.
  2. A Mock at 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 inside Describe/Context/It or 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 BeforeAll that 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-RestMethod but you mock Invoke-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."