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

8 Aug

This is part three 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 developed a Pester unit test for the New-SasToken helper function in the module I’m developing. In this post, I’m going to develop tests for the New-QueueMessage and Read-QueueMessage cmdlets. This post will also highlight using the Pester Mock command.

Mock

Good unit tests have several qualities. First (obviously), they should test your code. The trick here is to test your code without testing all the stuff your code depends on. So, the next good quality of a unit test is that it doesn’t have a bunch of external dependencies. Another good quality of a unit test is that it should be fast. On a large project, your test suite will grow to have hundreds if not thousands of tests. If a developer cannot run the test suite quickly, they’ll just stop running it. A test suite with a slow execution time quickly becomes a burden to the team rather than the asset it should be.

Tests that have external dependencies are slow. Consider code that calls a web service. First, the web service call itself is slow compared to code running on your development machine. Additionally, network latency may impact your test such that its unusable for other team members located around the world on a distributed team (a very common scenario here at Avanade). These problems are solved by mocking external dependencies. Pester has a Mock command that can mock most PowerShell cmdlets. I will use the Mock command to mock the Invoke-WebRequest cmdlet, and remove the dependencies my test suite would otherwise have on the Azure Service Bus REST API.

Unit Tests for New-QueueMessage

The parameter set for the New-QueueMessage cmdlet is in the first post in this series. Here it is again.

New-QueueMessage -Namespace <string> -QueueName <string> -Message <string> -CorrelationId <guid> -PolicyName <string> -Key <string>

Before I code the unit test, I’m going to add these parameters to my cmdlet. Here’s what the cmdlet looks like.

<# New-QueueMessage #>
function New-QueueMessage
{
	[CmdletBinding()]
	param
	(
		[Parameter(Mandatory=$true, Position=0)]
		[string] $Namespace,
		
		[Parameter(Mandatory=$true, Position=1)]
		[string] $QueueName,

		[Parameter(Mandatory=$true, Position=2)]
		[pscustomobject] $Message,

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

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

		[Parameter(Mandatory=$false)]
		[guid] $CorrelationId
	)

	throw [NotImplementedException]
}

The unit test will in some ways be much like the test for New-SasToken. The difference is that I need to create a mock for the Invoke-WebRequest cmdlet. Ideally, what my mock returns should look exactly like what the real Invoke-WebRequest cmdlet would return. The Microsoft documentation for Send Message shows the Response should look like this.

HTTP/1.1 201 Created
Transfer-Encoding: chunked
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Tue, 01 Jul 2014 23:00:22 GMT

0

Of course, Invoke-WebRequest does not simply return that text. It returns an object of type HtmlWebResponseObject. HtmlWebResponseObject doesn’t expose a usable constructor, so we can’t make one of those and have our mock return it. However, what we return from the mock just needs to be good enough. We’ll use a Hashtable.

Another consideration is what should New-QueueMessage return? I believe it should return nothing if it is successful. It should write any error messages to stderr (i.e. using the Write-Error cmdlet).

My unit test looks like this.

Describe "New-QueueMessage" {
	Context "Only required parameters in the correct order" {
		It "Should not return anything" {
			# Prepare
			$namespace = "sb-ycajp"
			$queueName = "usagerequest"
			$message = [pscustomobject] @{ "Body"="Test message"; }
			$key = "ggbkU/HOBDSYTTS0ljICEfn1dVdcxpfebcrAmR4HUXQ="
			$mockWebResponse = @{ "StatusCode"="201"; "StatusDescrip-tion"="Created"; "Headers"=@{"Transfer-Encoding"="chunked";"Strict-Transport-Security"="max-age=31536000"; "Content-Type"="application/xml; charset=utf-8";"Date"="Sat, 05 Aug 2017 23:16:50 GMT";"Server"="Microsoft-HTTPAPI/2.0"} }
			Mock Invoke-WebRequest { returns $mockWebResponse }
			
			# Operate
			$results = New-QueueMessage $namespace $queueName $message $key 2>&1

			# Assert
			[string]::IsNullOrEmpty($results) | Should Be $true
		}
	}
}

The redirection happening at the end of the call to New-QueueMessage redirects any error output to the $results variable, which should not contain anything if the test is successful.

When I run the tests, the results look like this.

Again, just what I expect to see: all tests failing with the NotImplementedException. This seems like another minor milestone, so I’m going to commit my changes to source control.

Unit Test for Read-QueueMessage

The unit test for Read-QueueMessage is much like the one for New-QueueMessage. The mock web response is a little different, but the procedure used to develop it is the same as that for New-QueueMessage. Just get the expected response from the Microsoft documentation and craft the Hashtable so it looks the same.

Here’s the Read-QueueMessage cmdlet after adding the parameters.

<# Read-QueueMessage #>
function Read-QueueMessage 
{
	[CmdletBinding()]
	param
	(
		[Parameter(Mandatory=$true, Position=0)]
		[string] $Namespace,
		
		[Parameter(Mandatory=$true, Position=1)]
		[string] $QueueName,

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

		[Parameter(Mandatory=$false)]
		[Switch] $Deadletter = [string]::Empty,

		[Parameter(Mandatory=$false)]
		[Switch] $Peek,

		[Parameter(Mandatory=$false)]
		[int] $Count,

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

		[Parameter(Mandatory=$false)]
		[guid] $CorrelationId
	)

	throw [NotImplementedException]
}

The Microsoft documentation indicates I will receive a Response that looks like this:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/atom+xml;type=entry;charset=utf-8
Server: Microsoft-HTTPAPI/2.0
BrokerProperties: {“DeliveryCount”:1,”EnqueuedSequenceNumber”:0,”EnqueuedTimeUtc”:”Tue, 01 Jul 2014 23:00:23 GMT”,”Label”:”M1″,”MessageId”:”3a146f76afee41648677887ffced72d8″,”SequenceNumber”:1,”State”:”Active”,”TimeToLive”:10}
Date: Tue, 01 Jul 2014 23:00:23 GMT

12
This is a message.
0

I don’t want to return most of that content as it’s related to the Http response, not the queue message. I plan to create a PSCustomObject that has the BrokerProperties and the body of the web response.

My unit test looks like this.

Describe "Read-QueueMessage" {
	Context "Only required parameters in the correct order" {
		It "Should return a message" {
			# Prepare
			$namespace = "sb-ycajp"
			$queueName = "usagerequest"
			$key = "ggbkU/HOBDSYTTS0ljICEfn1dVdcxpfebcrAmR4HUXQ="
			$body = "Test message"
			$mockWebResponse = @{ "StatusCode"="200"; "StatusDescription"="OK"; "Headers"=@{ "Transfer-Encoding"="chunked"; "Content-Type"="application/atom+xml;type=entry;charset=utf-8"; "Server"="Microsoft-HTTPAPI/2.0"; "BrokerProperties"="{""DeliveryCount"": 1, ""EnqueuedSequenceNumber"": 0, ""Enqueued-TimeUtc"":""Tue, 01 Jul 2014 23:00:23 GMT"", ""Label"":""M1"", ""Message-Id"":""3a146f76afee41648677887ffced72d8"", ""SequenceNumber"":1, ""State"":""Active"", ""TimeToLive"":10}"; "Date"="Tue, 01 Jul 2014 23:00:23 GMT" }; "Content"="$body" }
			Mock Invoke-WebRequest { return $mockWebResponse } 
			
			# Operate
			$message = Read-QueueMessage $namespace $queueName $key

			# Assert
			$message.Body | Should Be $body
		}
	}
}

I’ve only illustrated developing three unit-tests. The actual project has quite a few more ensuring that all of the envisioned scenarios work as expected.

In this post, I’ve continued the TDD approach to developing a PowerShell module using Pester for unit testing my code. I’ve highlighted using the Mock statement to mock a PowerShell cmdlet so my tests don’t have any external dependencies. So far, I’ve only developed the unit tests. In my next post, I will develop each of the functions in my module.

Resources:

Send Message (to an Azure Service Bus Queue)

Receive and Delete Message (Destructive Read)

Mike Williams

Software developer working with public cloud and hybrid cloud technologies since 2014 and System Center products since 2009 with heavy emphasis on system integration and enterprise cloud solutions.