Created
February 6, 2017 19:03
-
-
Save exospheredata/c93564b844a3d85d8dca2e3299cc16c1 to your computer and use it in GitHub Desktop.
Example Pester test for a PowerShell v5 Class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Begin Testing | |
try | |
{ | |
InModuleScope -ModuleName ToyBox -ScriptBlock { | |
Function Set-ObjectMockProperties ($object,[switch]$Exists) | |
{ | |
# This function will load the object and set the global script variables with the values supplied. | |
# By doing this, we can later easily change how we interact with the object and make edits along | |
# the way in other Mocks or Tests. | |
New-VerboseMessage 'Establish Aggregate Mock variables and settings' | |
[string]$script:___CurrentName=$object.Name | |
[string[]]$script:___CurrentState = $object.State | |
[int]$script:___CurrentFavorite = $object.Favorite | |
if ($Exists) {$script:DeadmanObjectCreatedTrigger = $true;} | |
else {$script:DeadmanObjectCreatedTrigger = $False;} | |
} | |
Function New-MockToyBox | |
{ | |
# This function will act as a Mock for our existing New-ToyBox function. The benefit to this behavior is that | |
# we can easily stub out the expected responses for the call. We have included a Switch parameter call Empty | |
# to allow us the ability to return either an item with default values (in the event that we want to test the output) | |
# or an empty object to which we can assign any value. | |
[CmdletBinding(supportsshouldprocess=$true)] | |
Param([switch]$empty) | |
$_mockToyBox = [ToyBox]::new() # The NEW() method is inherited by default for a class and does not need to be created. | |
if ($empty) | |
{ | |
# Since the empty parameter was sent, we will need to return just be new object with no data. | |
return $_mockToyBox; | |
} | |
else | |
{ | |
$_mockToyBox.Name = 'mytoy' | |
$_mockToyBox.State = 'Missing' | |
$_mockToyBox.Favorite = $false | |
return $_mockToyBox; | |
} | |
} | |
# This is a function of Mocks and makes life easier when we need to re-initialize Mocks between test suites. The benefit to | |
# to leveraging this function is seen when we have multiple suites that are not interdependent on each other. Each test should | |
# be treated as an independent state with no dependency on other tests. This will ensure that the code that we test meets all | |
# requirements and the correct behavior can be tested. | |
Function Set-MockInstances () | |
{ | |
Mock New-ToyBox{ | |
New-VerboseMessage 'Mock New-ToyBox' | |
if ($script:DeadmanObjectCreatedTrigger) { | |
$ToyBox = New-MockToyBox | |
$ToyBox.Name = $___CurrentName | |
$ToyBox.State = $___CurrentState | |
$ToyBox.Favorite = $___CurrentFavorite | |
return $ToyBox | |
} else { | |
return (New-MockToyBox -Empty) | |
} | |
} | |
} | |
# When testing large sections of code, it is always a good idea to distinguish messages from your test and any that may come from | |
# your code. This function will handle the way that we print out the code moving forward. | |
Function New-VerboseMessage ($message) | |
{ | |
$Classifier = '::PESTER:::::' | |
# NOTE: Interpolation of strings is a common trick that I use with messages. In this case, I have a message that I would like | |
# to inject the value of two variables. The '-f' identifies that these variables should be inserted into the string in and | |
# translated to their position in the array that follows. This allows me to easily make changes to the variables while maintaining | |
# and easy to read command. I typically use this technique in my large scripts, modules, and classes to streamline how I print the | |
# the output. | |
write-verbose -message ("{0}`t`t{1}" -f $Classifier, $Message) | |
} | |
# | |
# This an Array of properties that exist in our object and will be referenced in other tests to ensure | |
# all of the data is being returned from the Mocks. This should be a list of ALL the properties in | |
# the object scope. | |
# | |
$Test_Properties = @( | |
'Name' | |
, 'State' | |
, 'Favorite' | |
) | |
# | |
# Establish both Passing and Failing Test Parameter Objects for using in the validation tests to streamline | |
# the behavior of the tests. Not setting a property will accept our defaults from the Mock | |
$negativeTestParameters = @{ | |
Name = 'mytoy' | |
State = 'Missing' | |
Favorite = $false | |
} | |
$positiveTestParameters = @{ | |
Name = 'my new toy' | |
State = 'Playing' | |
Favorite = $true | |
} | |
# | |
# ToyBox::get() Validation Tests | |
# | |
Describe -Name 'ToyBox Get() Tests' -Tags @('ToyBox','ToyBoxGet','Unit','Get') -Fixture { | |
Set-MockInstances # Initiate the instances of Mock Servers and Environments for the suite | |
Context 'When the Toy does not exist' { | |
BeforeAll { | |
# The BeforeAll will be triggered before each indiviual test to ensure that the data is | |
# loaded and ready. This gives us a clean slate opportunity between tests. | |
Set-ObjectMockProperties -object $negativeTestParameters | |
} | |
# Since our method contains an overload for the Name property when searching, we are goung to create | |
# a new instance of the ToyBox by calling our invoke function 'New-ToyBox' which ultimately leverages | |
# our Mock and then initiate a call to GET() based on the Name property for our $negativeTestParameters | |
# variable. Since this item does not exist, it will return a $NULL name and missing. | |
$result = (New-ToyBox @negativeTestParameters).get($negativeTestParameters.Name) | |
It 'Should return the name as $Null and State as Missing' { | |
$result.Name | Should be $Null | |
$result.State | Should Be 'Missing' | |
} | |
It 'Should call the mock function New-ToyBox' { | |
# Asserting a Mock is a great way to ensure that the behavior in this test matches the path that | |
# we expect our code to take. This is also a great way to capture inefficient code paths (Am I | |
# making too many calls to the same function, Did I call something that I shouldn't, etc) | |
# | |
# If the code should NOT call a mock (For example, if we have a function that would be called on a Save), then | |
# we can easily Asset that the Mock is called -Times 0. This trick will help to ensure that we don't have a mistake | |
# in the code pattern. | |
Assert-MockCalled New-ToyBox -Exactly -Times 1 -Scope Context | |
} | |
}#context | |
Context 'When the ToyExists' { | |
BeforeAll { | |
# We are testing the behvaior that the object should exist when called. We can leverage this call and include our trigger | |
# parameter -Exists to set the DeadmanObjectCreatedTrigger variable to $true. This allows us to stub the output with a valid | |
# object as if it was previously saved. | |
Set-ObjectMockProperties -object $positiveTestParameters -Exists | |
} | |
# We have multiple tests that need to call on the same data. In this case, we will return to the variable the object and use it | |
# across the multiple tests. | |
$result = (New-ToyBox @positiveTestParameters).get($positiveTestParameters.Name) | |
It 'Should return the desired state as Stored' { | |
$result.Name | Should Be 'my new toy' | |
$result.State | Should Be 'Playing' | |
$result.Favorite | Should Be $True | |
} | |
It 'Should return the same values as passed as parameters' { | |
# Previously we declared an array with all of our property names. We will now loop through this to ensure that we are returning | |
# the correct values based on our expectations. This because a very value test when we start changing values in later tests. | |
Foreach ($t in ($Test_Properties| Where-object {$_})) | |
{ | |
New-VerboseMessage ("Testing ($t) : $($result.$($t)) | Should Be $( $positiveTestParameters.$($t) )") | |
$result.$($t) | Should Be $positiveTestParameters.$($t) | |
} | |
} | |
It 'Should call the mock function New-ToyBox' { | |
Assert-MockCalled New-ToyBox -Exactly -Times 1 -Scope Context | |
} | |
Assert-VerifiableMocks | |
}#context | |
} | |
} #InModuleScope | |
} # try | |
finally | |
{ | |
# Done | |
} # finally |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment