Lost in scopes

Welcome Forums Pester Lost in scopes

This topic contains 5 replies, has 4 voices, and was last updated by

 
Participant
1 month, 3 weeks ago.

  • Author
    Posts
  • #105454

    Participant
    Points: 0
    Rank: Member

    testmodule.psm1

    function Invoke-Public ($param)
    {
        Invoke-Private -param $param
    }
    function Invoke-Private ($param)
    {
        try
        {
            $Res = Resolve-DnsName -Name $param -DnsOnly -ErrorAction Stop
            return $Res
        }
        catch
        {
            return $_
        }
    }
    Export-ModuleMember -Function Invoke-Public

    testmodule.Tests.ps1

    Import-Module testmodule
    InModuleScope testmodule {
        $FQDN = "fqdn.domain.com"
        $Netbios = "hostname"
        Mock Resolve-DnsName {
            if ($Name -eq $FQDN)
            {
                return "true Name $Name. FQDN - $FQDN"
            }
            elseif ($Name -eq $Netbios)
            {
                return "false Name $Name. Netbios - $Netbios"
            }
            else
            {
                return "3rd Name $Name. FQDN - $FQDN. Netbios - $Netbios"
            }
        }
        Describe 'Private function test' {
            It 'Passes Invoke-Private with ' -TestCases @(
                @{Value = $FQDN; Expected = "true Name $FQDN. FQDN - $FQDN"}
                @{Value = $Netbios; Expected = "false Name $Netbios. Netbios - $Netbios"}
            ) {
                param ($Value, $Expected)
                Invoke-Private -param $Value | Should -Be $Expected
            }
        }
    }
    Describe 'Public function test' {
        $FQDN = "fqdn.domain.com"
        It 'Passes Invoke-Public' {
            Invoke-Public -param $FQDN | Should -Be "true Name $FQDN. FQDN - $FQDN"
        }
    }

    tests result:

    Executing Test
    Executing all tests in '..\test'

    Executing script C:\Temp\test\testmodule.Tests.ps1

    Describing Private function test
    [+] Passes Invoke-Private with 'fqdn.domain.com' 4.04s
    [+] Passes Invoke-Private with 'hostname' 284ms

    Describing Public function test
    [-] Passes Invoke-Public 1.21s
    Expected strings to be the same, but they were different.
    Expected length: 49
    Actual length: 45
    Strings differ at index 0.
    Expected: 'true Name fqdn.domain.com. FQDN – fqdn.domain.com'
    But was: '3rd Name fqdn.domain.com. FQDN – . Netbios – '
    ———–^
    32: Invoke-Public -param $FQDN | Should -Be "true Name $FQDN. FQDN – $FQDN"
    at , C:\Temp\test\testmodule.Tests.ps1: line 32
    Tests completed in 5.54s
    Tests Passed: 2, Failed: 1, Skipped: 0, Pending: 0, Inconclusive: 0

    Why the $FQDN and $Netbios variables are not available for mocked Resolve-DnsName function? How should I make them available, or what would be the recommended way to test in this scenario?

  • #105467

    Keymaster
    Points: 1,625
    Helping HandTeam Member
    Rank: Community Hero

    (Apologies for the spurious incorrect forum post you may have received; that was a bot that didn't catch the right keywords)

    Gimme a sec to try this on my own!

  • #105470

    Keymaster
    Points: 1,625
    Helping HandTeam Member
    Rank: Community Hero

    Yeah, so the problem is just that you've passed out of scope for the mock. Mocks are really designed to mock a function that is being run in a test; they're not designed to mock functions that exist elsewhere in a module. The module's "local" definition of the function or command "wins," because it's more local. Because you're calling a function, which is calling a function, the mock gets "lost," if you will.

    Struggling to explain, but maybe think of it this way: you've seen that you can go "one level" deep with Invoke-Private. What you can't do is go "deeper." Does that make sense?

    • #105629

      Participant
      Points: 0
      Rank: Member

      I am not sure I understand, to me it seems that the mock is working, it returns the mocked string just with empty variables. My observation is that the variables defined InModuleScope are available when calling mocked function in that same scope, but they are not available when calling in public scope (from within a public function).

      Now I'm on my phone and can't double check, but I think I was getting the expected result if I just used strings in if checks in mocked function, instead of using $fqdn and $netbios variables.

    • #105754

      Participant
      Points: -19
      Rank: Member

      Your observation is correct. The public function will not have access to those variables because it it outside the scope of where the mock is defined. If you move the public function test into that scope(InModuleScope testmodule) then the mocked outputs will be available to your public function.

  • #112183

    Participant
    Points: -19
    Rank: Member

    I am getting a different result than you do. The last Describe is placed outside of the InModuleScope, correct?

    Because in that case the Mock should not apply to the last describe, and that is what I am seeing. The real Resolve-DnsName is called.

    — In general about the scopes, mock is designed to mock stuff in different scopes, not just the one in the test. So what you are doing is correct. You are reaching into the TestModule scope, and shadowing Resolve-DnsName there. That means that any call to Resolve-DnsName within that scope will call the mock an not the real function. Even if you call Invoke-Public, it will reach into the TestModule module scope, and invoke Invoke-Private there. Inside it invokes Resolve-DnsName which is the mock.

    What would not work, and what people sometimes find confusing, is that we cannot overwrite the function directly – that is inject mock to the DnsClient module from which the Resolve-DnsName originates, because your module already got link to the functions from that module, and shadowing the functions in that module does not overwrite the link. So you are correctly mocking it in the scope of your mock, not in the scope of DnsClient.

You must be logged in to reply to this topic.