Part 2 – Developing the First Test Using Pester

This is part two of a multi-part post. Here are links to all the posts in this series.

Developing an Azure Service Bus PowerShell Module – Part 1/

Part 2 – Developing the First Test Using Pester

Part 3 – More Tests, this time with Pester's Mock

Part 4 – Coding the PowerShell Module

Introduction

In my last post, I covered getting started creating a PowerShell module that will allow my team to send and receive messages to Azure Service Bus Queues. In this post, I'm going to start developing the unit tests using Pester, PowerShell's unit test framework.

Test-Driven Development

Test-Driven Development (TDD) is a best practice no matter what language you develop with. Having a good suite of tests gives developers the freedom to change code without worrying about breaking the code (or at least they will immediately know when they have broken the code). Without a good suite of tests for your code, you risk code-rot. So, first I'm going to develop a couple of tests.

My colleague Chris Speers has blogged many times about the advantages of the Azure REST API's when compared to other options that are available. I want to avoid dependencies on other components, so it follows that the cmdlets in my module will be wrappers around the Azure Service Bus REST API's.

The first thing you need to do to use the REST API's is authenticate to them. There are two ways your client code can authenticate to Azure Service Bus: with a Simple Web Token (SWT) and with a Shared Access Signature (SAS) token. Microsoft recommends the Shared Access Signature approach. When you create an Azure Service Bus namespace, Azure will generate an SAS Policy and an SAS key that the Service Bus client code will use to authenticate to Azure Service Bus. While the initial default policy has all rights, SAS policies can be crafted so that specific clients have specific privileges scoped to specific Service Bus resources.

My module is going to need a helper function that creates an SAS token. So the first thing I do is create a stub for that function.

[powershell] <# New-SasToken #> function New-SasToken { throw [NotImplementedException] } [/powershell]

Because I plan for this to be an internal helper function, I do not add an Export-ModuleMember statement for that function to the module file. However, that means that the function will not be in scope in the Pester test. To test that function with Pester, I need to wrap the tests for that function in an InModuleScope block. InModuleScope gives Pester access to module members that are not public.

When the SAS token is created, it needs to have the expiration time encoded in it. Usually, you would set the token to expire a few minutes in the future. That means that a different token will be returned each time the test is run, and that's not what I want. I'm going to add the ability to pass an optional parameter that has the expiration time for the token. That way in my test, I can pass a specific expiration time for the token and compare it to a known good token.

The token also contains the name of the SAS Policy encoded in the token. As mentioned above, when you create the namespace using the Azure portal, it will automatically create an SAS Policy named RootManageSharedAccessKey. You can find these settings in Azure by clicking the Connection String link in the Overview blade for your Service Bus namespace.

Since new policies may be created, we need also be able to pass the policy name to the New-SasToken function. I will add an optional parameter with the default value of RootManageSharedAccessKey. Finally, the token also contains the name of the Service Bus namespace, so I need a parameter for that as well. My New-SasToken function stub now looks like this.

[powershell] <# New-SasToken #> function New-SasToken { param ( [Parameter(Mandatory=$true, Position=0)] [string]$Namespace,

[Parameter(Mandatory=$true, Position=1)] [string]$Key,

[Parameter(Mandatory=$false)] [string]$PolicyName = "RootManageSharedAccessKey",

[Parameter(Mandatory=$false)] [DateTime]$Expiry = $([DateTime]::UtcNow.AddSeconds(20 * 60)) )

throw [NotImplementedException] } [/powershell]

The next thing is to develop the unit test for this function. The unit test itself is drop-dead simple. Basically, I just call the New-SasToken function passing the correct parameters and compare the token that is returned to a known good token. But where do I get a known good token? That took a bit of research and I've attached links to the resources I used in the Resources section at the end of this post. Let's just say it involved reading the Microsoft documentation and running some great sample code that I found on MSDN. My unit test now looks like this.

[powershell]InModuleScope "Avanade.ServiceBus" { Describe "New-SasToken" { Context "Expiry Parameter" { It "Should return a specific token" { # Prepare $namespace = "sb-ycajp" $key = "ggbkU/HOBDSYTTS0ljICEfn1dVdcxpfebcrAmR4HUXQ=" $expiry = [DateTime]"1/1/1980 00:00"

# Operate $token = New-SasToken $namespace -Key $key -Expiry $expiry

# Assert $token | Should Be "SharedAccessSignature sr=sb-ycajp&sig=17PCSRT%2flklQiCnT4E0o1XmVxp%2fhM7xBvIf8UwC9tG4%3d&se=315532800&skn=RootManageSharedAccessKey" } } } } [/powershell]

When I run my test suite, I get the following.

As expected, all tests are failing, throwing a NotImplementedException. At this point, I've coded my first test that is more than just a placeholder. It seems like a good time to commit my changes to source control.

This post has covered the development of a unit test following the practice of Test-Driven Development. In my next post, I will add tests for the New-QueueMessage and the Read-QueueMessage cmdlets.

 

Resources:

Service Bus authentication with Shared Access Signatures

Service Bus HTTP Client