Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to Mock New-Object #311

Closed
tysonjhayes opened this issue Apr 16, 2015 · 6 comments
Closed

Unable to Mock New-Object #311

tysonjhayes opened this issue Apr 16, 2015 · 6 comments
Assignees

Comments

@tysonjhayes
Copy link

In my code I'm trying to mock a call to New-Object. I'm wondering if this is similar to #296

The relevant chunk of the code is as follows:

$shell = New-Object -ComObject 'Shell.Application'
$zip = $shell.NameSpace($zipFile)

All attempts to mock this code have been unsuccessful. Most of the time it acts as if I haven't actually called new object which results in $shell being null.

I've attempted to mock the functioning as such:

    $zipMock = New-Module -AsCustomObject -ScriptBlock {
        function NameSpace {
            New-Module -AsCustomObject -ScriptBlock {
                function Items {
                    return @{Name = 'TextFile.txt'}
                }
            }
        }
        Export-ModuleMember -Variable * -Function *
    }
    Mock New-Object -ModuleName ExamineContentZip -ParameterFilter {$ComObject -eq 'Shell.Application'} -MockWith {$zipMock}
    Mock New-Object -ModuleName ExamineContentZip -MockWith {$zipMock}
    function New-Object {return $zipMock}

This was done over three separate attempts each attempting a different mock or the finally the function. What's strange is that if I break into the function before calling $shell I can manually paste in the Mock code and it'll work. This only appears to fail if I'm calling it from the test.

@dlwyatt dlwyatt self-assigned this Apr 16, 2015
@dlwyatt
Copy link
Member

dlwyatt commented Apr 16, 2015

Haven't been able to reproduce this yet. I can't see your calling code in the ExamineContentZip module, but this attempt to reproduce the problem worked fine for me:

Describe 'Mocking Test' {
    $zipMock = New-Module -AsCustomObject -ScriptBlock {
        function Namespace {
            New-Module -AsCustomObject -ScriptBlock {
                function Items {
                    return @{Name = 'TextFile.txt'}
                }
            }
        }
        Export-ModuleMember -Variable * -Function *
    }

    Mock New-Object { return $zipMock } -ParameterFilter { $ComObject -eq 'Shell.Application' }

    It 'Calls the mock' {
        New-Object -ComObject Shell.Application | Should Be $zipMock
    }
}

@tysonjhayes
Copy link
Author

Module basically contains this:

function functiontotest
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string] $ZipFile
    )
    $shell = New-Object -ComObject 'Shell.Application'
    $breakvar = $true;
    $zip = $shell.NameSpace($zipFile)

    foreach($item in $zip.items())
    {
        if ($item.Name -eq "TextFile.txt")
        {
            return
        }
    }

        throw "zip does not have TextFile.txt present.";
}

Test is this:

Remove-Module [e]xamineContentZip -Force
$filePath = Join-Path -Path $PSScriptRoot -Child 'ExamineContentZip.psm1'
Import-Module -Name $filePath -DisableNameChecking -ErrorAction Stop

Describe 'functiontotest' {
    Context 'Should return null if TextFile is present' {
        $zipMock = New-Module -AsCustomObject -ScriptBlock {
            function NameSpace {
                New-Module -AsCustomObject -ScriptBlock {
                    function Items {
                        return @{Name = 'TextFile.txt'}
                    }
                }
            }
            Export-ModuleMember -Variable * -Function *
        }

        Mock New-Object { return $zipMock } -ParameterFilter { $ComObject -eq 'Shell.Application' } -ModuleName ExamineContentZip

        $expected = $null;
        $returned = functiontotest -ZipFile 'Test'

        It 'Should return null' {
            $returned | should be $expected
        }
    }
}

Right now when I remove the ModuleName flag I do not see the New-Object call mocked at all.

Mock New-Object { return $zipMock } -ParameterFilter { $ComObject -eq 'Shell.Application' }

Result:

   63:      $shell = New-Object -ComObject 'Shell.Application'
   64:*     $breakvar = $true;
   65:      $zip = $shell.NameSpace($zipFile)

[12:51:40 PM] [0008] » $shell

Application                                                 Parent
-----------                                                 ------
System.__ComObject                                          System.__ComObject

[12:51:42 PM] [0009] » $shell | gm
   TypeName: System.__ComObject#{286e6f1b-7113-4355-9562-96b7e9d64c54}

Name                 MemberType Definition
----                 ---------- ----------
AddToRecent          Method     void AddToRecent (Variant, string)
BrowseForFolder      Method     Folder BrowseForFolder (int, string, int, Variant)
<snip>

When I leave the ModuleName in it basically returns a null value.

[12:54:54 PM] [0013] » Invoke-Pester .\test.tests.ps1
Describing functiontotest
   Context Should return null if TextFile is present
Hit Variable breakpoint on '$breakvar' (Write access)

At C:\temp\ExamineContentZip.psm1:64 char:5
+     $breakvar = $true;
+     ~~~~~~~~~~~~~~~~~
[12:54:55 PM] [0013] » $shell
[12:55:24 PM] [0022] » $shell.gettype()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $shell.gettype()
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

@dlwyatt
Copy link
Member

dlwyatt commented Apr 16, 2015

Ah, you're running into issue #204 . It's sort of a bug, but it's one of those things I put off fixing for a major release since it might break people's existing scripts to fix it.

In the meantime, using the InModuleScope command might be a better option. Try this instead:

Remove-Module [e]xamineContentZip -Force
$filePath = Join-Path -Path $PSScriptRoot -Child 'ExamineContentZip.psm1'
Import-Module -Name $filePath -DisableNameChecking -ErrorAction Stop

InModuleScope ExamineContentZip {
    Describe 'functiontotest' {
        Context 'Should return null if TextFile is present' {
            $zipMock = New-Module -AsCustomObject -ScriptBlock {
                function NameSpace {
                    New-Module -AsCustomObject -ScriptBlock {
                        function Items {
                            return @{Name = 'TextFile.txt'}
                        }
                    }
                }
                Export-ModuleMember -Variable * -Function *
            }

            Mock New-Object { return $zipMock } -ParameterFilter { $ComObject -eq 'Shell.Application' }

            $expected = $null;
            $returned = functiontotest -ZipFile 'Test'

            It 'Should return null' {
                $returned | should be $expected
            }
        }
    }
}

Alternatively, don't assign your object to the $zipMock variable. Just put all of that New-Module code right into the -MockWith script block, and it would work that way as well. The problem is that the -MockWith block was being executed inside the module, and the $zipMock variable was out in your test script where the module couldn't see it.

@tysonjhayes
Copy link
Author

Crap I was afraid that's what I was running into. I tried both your suggestions and it started working. Could we highlight this in the documentation as I spent way to much time on this this morning. Totally get waiting for a major release to fix if its going to break things.

@dlwyatt
Copy link
Member

dlwyatt commented Apr 16, 2015

I think it's about time to just push for the next major release; we have several of these nagging things that are waiting to be fixed.

@tysonjhayes
Copy link
Author

Seems like a really good idea to me. This one in particular is kind of a painful bug to run into as its kind of difficult to track down exactly where the problem is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants