Reputation: 592
Need some help understanding how to unit test a PowerShell module I'm creating.
I'm using the PowerShellStandard.Library package to create some cmdlets. My commands are deriving from pscmdlet and not cmdlet since I do need to manipulate the SessionState variables.
I've created a new unit testing project and I've searched some tutorials (e.g. https://www.hanselman.com/blog/TestingPowerShellScriptsWithNUnit.aspx) where I found that I do need to use the Runspace class in order to execute the pscmdlet. Problem is that the below command, always returns a null runspace:
Runspace runspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault2());
Anyone have been experimenting with these lately and have any idea?
Upvotes: 1
Views: 1902
Reputation: 24394
I posted a similar question and the issue ended up being that I was trying to use the PowerShellStandard.Library
NuGet package as well. Instead, you want to use the Microsoft.PowerShell.SDK NuGet package.
I also have an example repo setup that shows how to run xUnit tests against a C# PowerShell Core cmdlet.
Upvotes: 2
Reputation: 9367
I wasn't able to get it working with a Runspace but I was able to unit test my PSCmdlets. Unfortunately, the solution I found wasn't very satisfying. I added a method to expose ProcessRecord
like so:
[Cmdlet(VerbsCommon.Get, "AnExample")]
public class ExampleCmdlet : PSCmdlet
{
public void ProcessInternal()
{
ProcessRecord();
}
}
When invoking the PSCmdlet from a unit test, I'd use this method:
var cmdlet = new ExampleCmdlet();
var proxy = new CommandRuntimeProxy();
cmdlet.CommandRuntime = proxy;
cmdlet.ProcessInternal();
var results = proxy.WrittenObjects.Select(obj => (T) obj).ToList();
CommandRuntimeProxy
is a new class that receives the objects. Here are the important parts, but there are more methods to implement.
internal class CommandRuntimeProxy : ICommandRuntime
{
public List<object> WrittenObjects { get; } = new List<object>();
public bool ShouldContinue(string query, string caption)
{
return true;
}
public bool ShouldProcess(string target)
{
return true;
}
public void ThrowTerminatingError(ErrorRecord errorRecord)
{
throw new InvalidOperationException("Error in pipeline", errorRecord.Exception);
}
public void WriteError(ErrorRecord errorRecord)
{
throw new InvalidOperationException("Error in pipeline", errorRecord.Exception);
}
public void WriteObject(object sendToPipeline, bool enumerateCollection)
{
WriteObject(sendToPipeline);
}
public void WriteObject(object sendToPipeline)
{
WrittenObjects.Add(sendToPipeline);
}
}
Effectively, you bypass most of the Powershell pipeline with this method. It seems to work reasonably well so far, so long as you use your hide access to the Powershell runtime behind interfaces for the benefit of your tests.
I believe the reason it's like this is because Powershell is quite a bit older and might not have seen quite as much love as the more recent MVC offerings from Microsoft. I hope this will change one day, but this is reasonable enough for now.
Upvotes: 1