Search code examples
powershellpesterpester-5

Can Pester v5 Mock command be called outside an It, Before or After block?


I have some old Pester v4 tests that are failing when I try to run them in Pester v5.

With Pester v4 I read that once Mock is called it will continue to apply for the rest of the Describe block, even if it was called within an It block. So I would call it from the Describe block, outside of an It block, to make it obvious it didn't just apply to a single It block.

That behaviour which worked in Pester v4 doesn't seem to work in Pester v5.

This is the script I'm writing tests against, MyScriptToTest.ps1:

function Get-FirstText ()
{
    return 'Some text'
}

function Get-SecondText ()
{
    return 'Other text'
}

function Get-Text ()
{
    $a = Get-FirstText
    $b = Get-SecondText

    return "$a; $b"
}

And here are the tests, in MyScriptToTest.Tests.ps1:

BeforeAll {
    . (Join-Path $PSScriptRoot 'MyScriptToTest.ps1')
}

Describe 'Get-FirstText' {
    It 'returns text "Some text"' {
        $result = Get-FirstText
            $result | Should -Be 'Some text'
        } 
}

Describe 'Get-Text' {
    It 'returns text "Some text; Other text"' {
        Get-Text | Should -Be 'Some text; Other text'
        } 

    Mock Get-FirstText { return 'Mock text' }
    
    It 'returns text "Mock text; Other text" after mocking Get-FirstText' {
        Get-Text | Should -Be 'Mock text; Other text'
        } 
}

The Pester v5 test output is as follows:

Describing Get-FirstText
  [+] returns text "Some text" when called indirectly 13ms (8ms|5ms)

Describing Get-Text
  [+] returns text "Some text; Other text" 66ms (54ms|13ms)
  [-] returns text "Mock text; Other text" after mocking Get-FirstText 42ms (41ms|1ms)
   Expected strings to be the same, but they were different.
   String lengths are both 21.
   Strings differ at index 0.
   Expected: 'Mock text; Other text'
   But was:  'Some text; Other text'
              ^
   at Get-Text | Should -Be 'Mock text; Other text', C:\...\MyScriptToTest.Tests.ps1:20
   at <ScriptBlock>, C:\...\MyScriptToTest.Tests.ps1:20
Tests completed in 565ms
Tests Passed: 2, Failed: 1, Skipped: 0 NotRun: 0

However, if I move the Mock command into the It block then it works:

BeforeAll {
    . (Join-Path $PSScriptRoot 'MyScriptToTest.ps1')
}

Describe 'Get-FirstText' {
    It 'returns text "Some text"' {
        $result = Get-FirstText
            $result | Should -Be 'Some text'
        } 
}

Describe 'Get-Text' {
    It 'returns text "Some text; Other text"' {
        Get-Text | Should -Be 'Some text; Other text'
        }   
   
    It 'returns text "Mock text; Other text" after mocking Get-FirstText' {
        Mock Get-FirstText { return 'Mock text' }
        Get-Text | Should -Be 'Mock text; Other text'
        } 
}

I haven't figured out the new constraints around calling the Mock command in Pester v5. Can the Mock command still be called within a Describe or Context block, outside of an It block? Or does it now always have to be called within an It block?


Solution

  • The docs describe the v5+ change of where Mock commands must (now) be placed (emphasis added):

    Mocks are no longer effective in the whole Describe / Context in which they were placed. Instead they will default to the block in which they were placed.

    That is, a Mock command placed directly inside Describe or Context commands - as in the code in your question - is now effectively ignored.

    To spell it out: In Pester v5+, for a Mock statement to be effective, it must be placed inside one of the following constructs:

    • Inside a BeforeAll command, which therefore applies to all tests in the same scope.

      • A BeforeAll command may be placed directly inside a Describe command or - for more fine-grained control - inside a nested Context command.
    • Inside an individual It command, in which case it applies to that test only.


    In the context of your code, this means:

    Context-level Mock:

    Describe 'Get-Text' {
        It 'returns text "Some text; Other text"' {
            Get-Text | Should -Be 'Some text; Other text'
            } 
    
        Context 'With Mock' {
    
          BeforeAll {
            # Placing the Mock here works, and makes it take effect
            # for all tests in this context.
            Mock Get-FirstText { return 'Mock text' }
          }
    
          It 'returns text "Mock text; Other text" after mocking Get-FirstText' {
              Get-Text | Should -Be 'Mock text; Other text'
              } 
    
          # ... potentially more `It` commands to which the Mock applies.
        }
    }
    

    It-level Mock:

    Describe 'Get-Text' {
        It 'returns text "Some text; Other text"' {
            Get-Text | Should -Be 'Some text; Other text'
            } 
    
        
        It 'returns text "Mock text; Other text" after mocking Get-FirstText' {
            # Placing the Mock here works, and makes it take effect
            # for this specific test only.
            Mock Get-FirstText { return 'Mock text' }
            Get-Text | Should -Be 'Mock text; Other text'
            } 
    }