Showing posts with label Unit testing. Show all posts
Showing posts with label Unit testing. Show all posts

Sunday, February 22, 2015

Create stub objects as arguments for system under test

Sometimes you need to pass a stub object as an argument to a function or script that is under test. FluentShellUnit API exposes extension methods to add methods and properties on the PSObject so that the object can have the desired behavior when used in the system under test.
Function Get-VirtualDirectoryForWebApp
{
       param
       (
              $WebApplication,
              [String] $Zone
       )
       $path = $Webapplication.GetIisSettingsWithFallback($Zone).Path
       $path
}

The Get-VirtualDirectoryForWebApp returns the location of the virtual directory for a SharePoint webapplication by using the IisSettings object for a Zone. If you want to test this method, you need to stub a SharePoint WebApplication and then pass it to the method.
As mentioned before, the first step is to create a PSObject and then add the required methods and properties to the object.

var webApplication = new PSObject();
var iisSettings = new PSObject();
var path = @"C:\MyWeb\wss\mywebapp";
iisSettings.StubProperty("Path", path);
webApplication.StubMethod("GetIisSettingsWithFallback", objects => objects[0].ToString() == "Default" ? iisSettings : null);

The StubMethod accepts a PsDelegate implementation that accepts a param object[] and returns an object.
public delegate object PsDelegate(params object[] argsObjects);


Now you can use this stub object as an argument to the Get-VirtualDirectoryForWebApp method and get the desired output.

var actual = PsFactory.Create(HostState.Core)
    .FailOnNonTerminatingError()
    .Load(@"TestModule\Modules\TestModule.psm1")
    .Execute
    (
        "Get-VirtualDirectoryForWebApp",
        new Dictionary<string, object>
        {
            {"WebApplication", webApplication},
            {"Zone", "Default"}
        }
    )
    .FirstResultItemAs<string>();

Assert.IsTrue(actual == path);

You can download the latest version from github or the package from Nuget.

Saturday, February 7, 2015

FluentShellUnit - Create a stub from script block

A stub is a test-specific replacement for a real object that feeds the desired indirect inputs to the system under test. FluentShellUnit supports stubbing by using a scriptblock that is loaded from a script file if the input to be provided by the stub/ the return value is a complex type. If the return value for a stub is a simple string, then you don’t need to use a script block.
For e.g, the below given method in the module calls the Get-SPWebApplication cmdlet that returns a SharePoint web application while invoked in the execution process of the method Get-WebApplicationTest

Function Get-WebApplicationTest{
      param([string] $webAppName)
      $webApp = Get-SPWebApplication -Name $webAppName
      return $webApp
}
In a test execution context, you don’t want to invoke the Get-SPWebApplication cmdlet using the SharePoint API. To create a stub for the Get-SPWebApplication cmdlet, you can now create a script file that contains a method Get-SPWebApplication and use your custom implementation there.

Function Get-SPWebApplication{
      param([string]$Name)
      @{"Url" = "http://dummywebapplication.nl"; "Description" = "Description of the webapplication"}
}

I’ve created a scriptfile WebApplication.ps1 that contains this method.
While invoking the Get-WebApplicationTest method from the FluentShellUnit tests, you can now pass this scriptblock to use as a stub to the actual Get-SPWebApplication cmdlet like

var actual = PsFactory.Create(HostState.Core)
                .Load(@"TestModule\Modules\TestModule.psm1")
                .StubFromFile("WebApplication.ps1")
                .Execute
                (
                    "Get-WebApplicationTest",
                    new Dictionary<string, string>
                    {
                        {"webAppName", "MySPWebApplication"}
                    }
                )
                .FirstResultItemAs<Hashtable>();
Assert.IsTrue(actual.Contains("Url"));




FluentShellUnit - Create a dummy object


In the previous entry of this series of FluentShellUnit, we saw how to use the framework to invoke a method on a PowerShell module and perform assertions. This article we’ll see how we can pass in parameters to a method and use a simple stub/ dummy object for a PowerShell cmdlet to isolate our code under test.
FluentShellUnit’s execute method’s overload takes a Dictionary as argument that is used to pass the parameters to the calling method. For e.g. The below method, passes the value “VSTest” to the parameter “context” for the Get-WelcomeMessage function in the TestModule

var actual = PsFactory.Create(HostState.Core)
                .Load(@"TestModule\Modules\TestModule.psm1")
                .Execute
                (
                    "Get-WelcomeMessage",
                    new Dictionary<string, string>
                    {
                        {"context", "VSTest"}
                    }
                )
                .FirstResultItemAs<string>();
Assert.IsTrue(actual.Contains("VSTest"));
                    
Function Get-WelcomeMessage{
      param([string] $context)
     
      "Welcome from context {0}" -f $context
}

FluentShellUnit also allows you to create a simple Dummy object (http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html) , that has no implementation and does not do anything when invoked. The below example shows how to create a Dummy for the Write-Host cmdlet when invoked from a method.


var actual = PsFactory.Create(HostState.Core)
                .Load(@"TestModule\Modules\TestModule.psm1")
                .Stub("Write-Host")
                .Execute
                (
                    "Get-WelcomeMessage",
                    new Dictionary<string, string>
                    {
                        {"context", "VSTest"}
                    }
                )           
                .FirstResultItemAs<string>();
Assert.IsTrue(actual.Contains("VSTest"));

Where the Get-WelcomeMessage implementation is as

Function Get-WelcomeMessage{
      param([string] $context)
      $message = "Welcome from context {0}" -f $context
      Write-Host "Setting the welcome message as {0}" -f $message
      $message
}

Introducing FluentShellUnit - A unit testing framework for PowerShell in C#

FluentShellUnit is a unit testing framework of testing PowerShell scripts and modules. It provides a simple to use API to write your tests in C# for loading and executing the functions in a PowerShell module or script file. It follows a very easy approach to stub the existing cmdlets or functions so that you can easily isolate your test code. This makes it a very good candidate for teams practicing TDD and using PowerShell in their code base.

Using the framework:

You can download the entire source code from the GitHub repository or use the Nuget package that is available from the Nuget gallery in Visual studio.
To download the source code and compile it to use in you projects visit the GitHub page at https://github.com/prajeeshprathap/FluentShellUnit.
To download the nuget package, open the package download manager from visual studio and search for FluentShellUnit. Click install to install the package to your project.

Creating your first test.

Create a new Unit test project in C# and a new test class to the project
Use the DeploymentItem attribute in the class to deploy the PowerShell modules to the test execution local folder if you don’t want to refer the modules from a fixed location in your drive. This approach will ensure that the modules are not loaded from an absolute path on the developer’s machine that created the tests. With the use of DeploymentItem you can also ensure that, the tests will work fine in the build server as well.

[TestClass]
[DeploymentItem(@"Data\Modules\Host\Host.psm1""Modules")]
public class HostTests

Create a test method and ues the PsFactory.Create method to create a new intsance of the PowerShell host that allows you to load and execute tests.
[TestClass]
[DeploymentItem(@"Data\Modules\Host\Host.psm1""Modules")]
public class HostTests
{
    [TestMethod]
    [TestCategory("Host Module")]
    public void ConfirmLocalSession_should_return_true_if_tests_are_executed_in_the_local_machine()
    {
        var actual = PsFactory.Create(HostState.Core);                   
    }
}

The load method will load a module into the runspace from a path mentioned as parameter to the method. If you have not used the DeploymentItem attribute, then you can make use of the IsAbsolute overload of the method and pass the absolute path instead of a relative path.
var actual = PsFactory.Create(HostState.Core)
            .Load(@"Modules\Host.psm1");          

To execute a method from the loaded module call the Execute method with the method name. The example show in this sample is calling a method that does not except any parameters. We’ll see later how to pass parameters to the method if needed.

var actual = PsFactory.Create(HostState.Core)
            .Load(@"Modules\Host.psm1")
            .Execute("Confirm-LocalSession");

Finally the ResultAs and FirstResultitemAs methods will tanslate the result of execution into a type mentioned and can be used for assertions in the code.
var actual = PsFactory.Create(HostState.Core)
            .Load(@"Modules\Host.psm1")
            .Execute("Confirm-LocalSession")
            .FirstResultItemAs<bool>();

        Assert.IsTrue(actual);

Wednesday, January 21, 2015

Creating your own unit test framework for PowerShell - Part 6


The authorization manager helps control the execution of commands for the runspace. When you try to execute a PowerShell script from C#, and haven't changed PowerShell's default execution policy, the scripts that are executed under the execution policy set on the machine. If you want the tests executed from C# to bypass the default security policy, then you need to either use a null AuthorizationManager implementation for the runspace or create a custom implementation of the AuthorizationManager and override the policy based on any condition you have. Deriving from the AuthorizationManager class allows you to override the ShouldRun method and add the logic specific to your needs like set up a reason parameter with a custom execption with proper explanation and details on why this command was blocked etc.
In the testing framework, I decided to use the second approach and created the custom authorization manager implementation as

internal class TestContextAuthorizationManager : AuthorizationManager
{
    public TestContextAuthorizationManager(string shellId) : base(shellId)
    {

    }

    protected override bool ShouldRun(CommandInfo commandInfo, CommandOrigin origin, PSHost host, out Exception reason)
    {
        base.ShouldRun(commandInfo, origin, host, out reason);
        return true;
    }
}

In the LoadPSTestHost method you can now use this implementation instead of the default AuthorizationManager as
var state = InitialSessionState.CreateDefault2();
state.AuthorizationManager = new TestContextAuthorizationManager("VSTestShellId");

Monday, January 19, 2015

Creating your own unit testing framework for PowerShell - part 5


PowerShell cmdlets and modules can report two kinds or errors (Terminating and non-terminating). Terminating errors are errors that cause the pipeline to be terminated immediately, or errors that occur when there is no reason to continue processing. Nonterminating errors are those errors that report a current error condition, but the cmdlet can continue to process input objects. With nonterminating errors, the user is typically notified of the problem, but the cmdlet continues to process the next input object. Terminating errors are reported by throwing exceptions or by calling the ThrowTerminatingError method, while non-terminating errors are reported by calling the Write-Error method that in turn sends an error record to the error stream.
To capture all the non-terminating errors you have to probe the PowerShell.Streams.Error collection and collect the details of the errors. While terminating errors are throw as RuntimeException and can be handled at the catch block.
In our framework, I’ve extended the FunctionInfo object to expose a property to capture non-terminating errors and also provided an option to expose the non-terminating error as a RuntimeException if needed by using the FailOnNonTerminatingError method.
public PsHost FailOnNonTerminatingError()
{
    _failOnNonTerminatingError = true;
    return this;
}

The implementation for the handle errors looks like
private string HandleNonTerminatingErrors(System.Management.Automation.PowerShell shell)
{
    var errors = shell.Streams.Error;
    if (errors == null || errors.Count <= 0) return String.Empty;
    var errorBuilder = new StringBuilder();
    foreach (var err in errors)
    {
        errorBuilder.AppendLine(err.ToString());
    }
    if (_failOnNonTerminatingError)
    {
        throw new RuntimeException(errorBuilder.ToString());
    }
    return errorBuilder.ToString();
}

Now in the code, you can use the test methods as.
[TestMethod]
[ExpectedException(typeof (RuntimeException))]
public void Tests_PsHost_FailOnNonTerminatingError_ThrowsNonTerminatingErrorsAsRuntimeExceptions()
{
    PsHost<TestModule>
        .Create()
        .FailOnNonTerminatingError()
        .Execute("Invoke-NonTerminatingError");
}

Next we’ll see how to overcome the execution policies in the unit test context without altering the PowerShell environment policies.