PowerShell Pester Mock Rest API Calls
Asked Answered
O

2

6

Is there any simple approach on how to mock a Rest API Calls in Pester.

Here is my code, I just need to mock those Rest API Calls in Pester and test them, could someone help me here.

Describe 'Test worldclockapi.com' {
    BeforeAll {
        $response = Invoke-WebRequest -Method 'GET' -Uri 'http://worldclockapi.com/api/json/utc/now'
        $responseContent = $response.Content | ConvertFrom-Json
        Write-Output $responseContent
    }

    It 'It should respond with 200' {
        $response.StatusCode | Should -Be 200
    }
    
    It 'It should have a null service response' {
        $responseContent.serviceResponse | Should -BeNullOrEmpty
    } 
    
    It 'It should be the right day of the week' {
        $dayOfWeek = (Get-Date).DayOfWeek
        $responseContent.dayOfTheWeek | Should -Be $dayOfWeek
    }
    
    It 'It should be the right year' {
        $year = Get-Date -Format 'yyyy'
        $responseContent.currentDateTime | Should -BeLike "*$year*"
    }
    
    It 'It should be the right month' {
        $month = Get-Date -Format 'MM'
        $responseContent.currentDateTime | Should -BeLike "*$month*"
    }
    
    # These two tests assume you are running this outside daylight savings (during the winter) .. hacky but good way to showcase the syntax ;)
    It 'It should not be daylight savings time' {
        $responseContent.isDayLightSavingsTime | Should -Not -Be $true
    }
    
    It 'It should not be daylight savings time another way' {
        $responseContent.isDayLightSavingsTime | Should -BeFalse
    }
}
Ochoa answered 25/7, 2021 at 23:27 Comment(1)
You need to mock the Get-RestMethod commandKriss
T
6

It's probably easiest to use a real response as a template for your mock output.

Invoke-WebRequest -Method 'GET' -Uri 'http://worldclockapi.com/api/json/utc/now' | 
    Export-Clixml .\response.xml

The above command will serialize a real response from the API to a file.

Now we can import the file to use with our mock. All it takes is to use the Mock command to define our mock.

Mock

  • -CommandName : command we are mocking
  • -ParameterFilter: An optional filter to limit mocking behavior only to usages of CommandName where the values of the parameters passed to the command pass the filter. This ScriptBlock must return a boolean value.
  • -MockWith: A ScriptBlock specifying the behavior that will be used to mock CommandName. The default is an empty ScriptBlock. It is here that we import the file to be our output.
Mock -CommandName Invoke-WebRequest -ParameterFilter { $Method -eq 'GET' } -MockWith { Import-Clixml .\response.xml }

Now when Invoke-WebRequest is called with -Method 'Get' our mock will be called instead and will return our object which we import using Import-CliXml (or other method - json, xml, etc.
Note: the mock command must come before any calls to the command we are mocking. Also note that the mock will be called even inside other functions that use the command.

    BeforeAll {
        Mock -CommandName Invoke-WebRequest -ParameterFilter { $Method -eq 'GET' } -MockWith { Import-Clixml .\response.xml }

        # on this next line our mock will be called instead and will return our prepared object
        $response = Invoke-WebRequest -Method 'GET' -Uri 'http://worldclockapi.com/api/json/utc/now'
        $responseContent = $response.Content | ConvertFrom-Json
        Write-Output $responseContent
    }
Triacid answered 26/7, 2021 at 6:27 Comment(0)
I
4

I think Daniel's answer is great, but if you are working on a large or shared repository then you just need to be careful about managing those XML files too. Another option, which I use, is to have one large Json file for all your returned objects using real responses. It can be imported in either BeforeAll or BeforeDiscovery depending on how your tests are structured.

The reason for my supplementary answer is really just to cover error responses too, because it is important to have test cases that show how you deal with a REST call failing. Wrapping Invoke-WebRequest in your own function might be useful for returning personalised errors, handling header responses, and having constants for a site name or an allowed set of API paths. Depending on the version of PowerShell, this is how I might handle a 404, for example.

Context " When a path does not exist in the API" {
    BeforeAll {
        Mock Invoke-WebRequest {
            # Use the actual types returned by your function or directly from Invoke-WebRequest.
            if ($PSVersionTable.PSEdition -eq "Desktop") {
                $WR = New-MockObject -Type 'System.Net.HttpWebResponse'
                $Code = [System.Net.HttpStatusCode]::NotFound
                # Use Add-Member because StatusCode is a read-only field on HttpWebResponse
                $WR | Add-Member -MemberType NoteProperty -Name StatusCode -Value $Code -Force
                $Status = [System.Net.WebExceptionStatus]::ProtocolError
                $Ex = [System.Net.WebException]::new("404", $null, $Status, $WR)
            }
            else {
                $Message = [System.Net.Http.HttpResponseMessage]::new()
                $Message.StatusCode = [System.Net.HttpStatusCode]::NotFound
                $Ex = [Microsoft.PowerShell.Commands.HttpResponseException]::new("404", $Message)
            }
            throw $Ex
        } -ParameterFilter {
            $Uri -eq "http://worldclockapi.com/api/json/utc/NEVER" -and
            $Method -eq "Get"
        }

        $GetRestTimeParams = @{
            Uri   = "http://worldclockapi.com/api/json/utc/NEVER"
            Method = "Get"
        }
    }

    It " Get-RestTime should not run successfully" {
        { Get-RestTime @GetRestTimeParams } | Should -Throw
    }

    It " Get-RestTime should throw a 404 error" {
        $ShouldParams = @{
            # Use the actual types returned by your function or directly from Invoke-WebRequest.
            ExceptionType   = [System.Net.WebException]
            ExpectedMessage = "404: NotFound"
        }
        {
            Get-RestTime @GetRestTimeParams
        } | Should -Throw @ShouldParams
    }
}
Incept answered 26/7, 2021 at 7:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.