Mocking New-PSSession and Invoke-Command

1,055 views
Skip to first unread message

Matthew Hatch

unread,
Jun 3, 2015, 2:09:20 PM6/3/15
to pes...@googlegroups.com
So I'm testing a cmdlet that makes remote calls, but I don't want to actually make the remote calls in the test, so I need to at least mock Invoke-Command.  But I am making a few remote calls so I setting up a Session and running invoke-command passing the PSSession to Session parameter. So I would like to Mock up New-PSSession.  Since I'm calling Invoke-Command and passing the results of New-PSSession, I need to return a PSSession object from the Mock.

Here is the module code:

    PROCESS{
            $Session
= New-PSSession -ComputerName $ComputerName -Credential $Credential
           
           
Invoke-Command -Session $Session -ScriptBlock {Import-Module WebAdministration}
           
           
if(!($PSBoundParameters.ContainsKey('Name'))){$Name = Invoke-Command -Session $Session -ScriptBlock $GetAllAppPools}
           
           
Invoke-Command -Session $Session -ScriptBlock $GetAppPoolMetaData
       
   
}


Here is the tests:

Describe 'Get-IISAppPool' {
    $secpasswd = ConvertTo-SecureString 'PlainTextPassword' -AsPlainText -Force
    $Cred = New-Object -TypeName PSCredential('username',$secpasswd)
   
     Mock -CommandName New-PSSession -ModuleName IIS -MockWith {
        
          return $true     
    }
   
    Mock -CommandName Invoke-Command -ModuleName IIS -MockWith {
        return $true
    }
   
    Context 'Remote AppPool' {
        Get-IISAppPool -ComputerName 'Server0001' -Name 'TestAppPool' -Credential $Cred
        It 'Should call New-PSSession 1 time'{
            Assert-MockCalled -Exactly 1
        }
    }
}


The issue is I need to pass a valid PSSession object to Invoke-Command and I don't think I can return that from the Mock. 

Is there a better way for me to Mock this?   Or do I need to think about writing code differently in order to make it more testable?

Thanks


Dave Wyatt

unread,
Jun 3, 2015, 2:36:35 PM6/3/15
to pes...@googlegroups.com
That's tricky.  I'll look into it.

Off the top of my head, you have two options.  You can create an actual PSSession object to localhost from your mock of New-PSSession, and then your calls to Invoke-Command would work.  However, this would mean that the machine running tests needs to have PSRemoting enabled, and you'd need to be running the tests as a user who can access that PSSession.  That's not ideal, but would definitely work if the prerequisites are met.

Another option is to try to create a PSSession object (or a subclass, if it's not sealed) from your mock which can be passed on to Invoke-Command, but doesn't need to be a live session; it just has to be of the right type.  If we can make that work, it would make for more reliable test code.

Matthew Hatch

unread,
Jun 3, 2015, 2:55:23 PM6/3/15
to pes...@googlegroups.com
I tried to create a PSSession object using New-PSSession from within the Mock, but it looks like it creates an endless loop.

And I tried to create a PSSession object using New-Object, but it doesn't look like there is a constructor (is that what you mean by sealed?).  I agree that creating a PSSession object would be ideal since it would make the test work without any other dependencies.  I'll keep looking as well...


Thanks for taking a look.

Dave Wyatt

unread,
Jun 3, 2015, 3:44:31 PM6/3/15
to pes...@googlegroups.com
Unfortunately, I think the second option is out.  PSSession is a sealed class, and its only constructor probably requires you to set up an actual live session.  So we're down to just making sure that PSRemoting is enabled on the system where you run the tests, and creating a live session to use in your mock.

Avoiding that infinite recursion is simple enough; you just use a module-qualified call to New-PSSession to bypass mocking.  Something like this:

$mockSession = Microsoft.PowerShell.Core\New-PSSession -ComputerName localhost -ErrorAction Stop

Mock New-PSSession { return $mockSession }
Mock Invoke-Command { ... }

Matthew Hatch

unread,
Jun 3, 2015, 4:01:15 PM6/3/15
to pes...@googlegroups.com
Thanks, that's a nice little trick using the qualified name... not ideal, but its the best we can do since the Class is sealed.

Thanks for your help!


On Wednesday, June 3, 2015 at 2:09:20 PM UTC-4, Matthew Hatch wrote:

Tore Groneng

unread,
May 4, 2016, 11:13:38 AM5/4/16
to Pester
Go hit by this issue today myself. An other option I am considering is using Export-Clixml for an PSSession object. Then we can just use import-clixml in the test and be done with it?

Cheers

Tore

Dexter Dhami

unread,
May 18, 2016, 6:58:29 AM5/18/16
to Pester
Probably it won't work as the PSSession object would be deserialized. 
I have been using the above mentioned  method of creating a PSSession to the localhost, but you need to clean up the created PSSession or otherwise you might hit the max no of concurrent sessions for a User error.


Message has been deleted

Alex Zammitt

unread,
Jan 9, 2020, 1:42:19 PM1/9/20
to Pester
I've been able to mock a PSSession like this:

[System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([System.Management.Automation.Runspaces.PSSession]);

I haven't tried to set properties on the mock, but you should be able to following this strategy:

-Create instance of sealed class without getting constructor called.
    System.Runtime.Serialization.FormatterServices.GetUninitializedObject(instanceType);
-Assign values to your properties / fields via reflection
    YourObject.GetType().GetProperty("PropertyName").SetValue(dto, newValue, null);
    YourObject.GetType().GetField("FieldName").SetValue(dto, newValue);

I hope this helps someone!
Reply all
Reply to author
Forward
0 new messages