Test mock is called with correct parameters

73 Views Asked by At

I have a function that uses Invoke-RestMethod to send data to an API. The API accepts data in different ways (Body, Header, QueryString) so the function takes in the data plus 2 parameters that determine how the data will be sent. Using a switch statement the data is added to the corresponding HashTable and then passed as a parameter to Invoke-RestMethod. Function code is below.

Function

function Invoke-GetRequest {
    param(
        [Parameter(Mandatory)]
        [String] $Url,
        [Parameter(Mandatory)]
        [String] $TechnicianKey,
        [ValidateNotNullOrEmpty()]
        [String] $InputData,
        [ValidateSet("Body","Header","QueryString")]
        [String] $SendTechnicianKeyIn="Header",
        [ValidateSet("Body","QueryString")]
        [String] $SendInputDataIn="Body"
    )

    $header = @{}
    $data = @{}
    $queryString = @{}

    switch($SendTechnicianKeyIn) {
        "Body" {$data.Add('TECHNICIAN_KEY',$TechnicianKey)}
        "Header" {$header.Add('TECHNICIAN_KEY', $TechnicianKey)}
        "QueryString" {$queryString.Add('TECHNICIAN_KEY', $TechnicianKey)}
    }

    switch($SendInputDataIn) {
        "Body" {$data.Add('input_data', $InputData)}
        "QueryString" {$queryString.Add('input_data', $InputData)}
    }

    if($queryString.Count -gt 0) {
        $uri = Format-HttpQueryString -Uri $Url -QueryParameter $queryString
    }
    else {
        $uri = $Url
    }

    $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header -Body $data -ErrorAction Stop
    $response
}

I am writing unit tests using Pester so have mocked Invoke-RestMethod and I need to verify that the data is being added to the correct HashTable and is therefore Invoke-RestMethod is called with the correct parameters.

I have not found a way to directly test the HashTables so the only way I can think to do this is to use a ParameterFilter on the Mock so that it is only called if the filter matches but if I do that and the filter doesn't match then a call goes out to the real Invoke-RestMethod which obviously is not wanted in a unit test. Below is an example of the Pester test.

Pester Test

Context 'Valid Calls' {

    BeforeAll {
        $splat = @{
            Url = 'https://www.test.com/api/'
            TechnicianKey = '00000000-0000-0000-0000-000000000000'
            InputData = '{"list_info":{"row_count":"10"}}'
        }
    }
        
    Describe 'TechnicianKey Header, InputData Body Default' {
            
        BeforeAll {
            Mock Invoke-RestMethod -ParameterFilter { $uri -eq $splat.Url -and $header.TECHNICIAN_KEY -eq $splat.TechnicianKey -and $data.input_data -eq $splat.InputData }} {}
        }

        It 'Should invoke Invoke-RestMethod once' {
            Invoke-GetRequest @splat
            Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
        }
    }
}
1

There are 1 best solutions below

2
On

Posting my final solution for future reference. I used the ParameterFilter with the different values for the Uri, Headers and Body so that an error would be thrown if the Mock never fired. This still doesn't fully solve the problem though because say I was refactoring/updating this code in the future and I make a change so that the filter no longer matches, then this is going to make a real call to Invoke-RestMethod.

Describe 'Unit: Invoke-GetRequest' -Tag Unit {

    Context 'Valid Calls' {

        BeforeAll {
            $splat = @{
                Url = 'https://test.com/api/'
                TechnicianKey = '00000000-0000-0000-0000-000000000000'
                InputData = '{"list_info":{"row_count":"10"}}'
                SendTechnicianKeyIn = 'Header'
                SendInputDataIn = 'Body'
            }

            Mock Invoke-RestMethod -ParameterFilter { $uri -eq $splat.Url -and $data.TECHNICIAN_KEY -eq $splat.TechnicianKey -and $data.input_data -eq $splat.InputData } {}
            Mock Invoke-RestMethod -ParameterFilter { $uri -eq 'https://test.com/api/?TECHNICIAN_KEY=00000000-0000-0000-0000-000000000000' -and $data.input_data -eq $splat.InputData } {}
            Mock Invoke-RestMethod -ParameterFilter { $uri -eq 'https://test.com/api/?input_data={"list_info":{"row_count":"10"}}' -and $data.TECHNICIAN_KEY -eq $splat.TechnicianKey } {}
            Mock Invoke-RestMethod -ParameterFilter { $uri -eq $splat.Url -and $header.TECHNICIAN_KEY -eq $splat.TechnicianKey -and $data.input_data -eq $splat.InputData } {}              
            Mock Invoke-RestMethod -ParameterFilter { $uri -eq 'https://test.com/api/?TECHNICIAN_KEY=00000000-0000-0000-0000-000000000000' -and $data.input_data -eq $splat.InputData } {}
            Mock Invoke-RestMethod -ParameterFilter { $uri -eq $splat.Url -and $header.TECHNICIAN_KEY -eq $splat.TechnicianKey -and $data.input_data -eq $splat.InputData } {}
        }
        
        Describe 'TechnicianKey Body, InputData Body' {

            BeforeAll {
                $splat.SendTechnicianKeyIn = 'Body'
                $splat.SendInputDataIn = 'Body'
            }

            It 'Should invoke Invoke-RestMethod once' {
                Invoke-GetRequest @splat
                Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
            }
        }
        
        Describe 'TechnicianKey QueryString, InputData Body' {

            BeforeAll {

                Mock Format-HttpQueryString {
                    'https://test.com/api/?TECHNICIAN_KEY=00000000-0000-0000-0000-000000000000'
                }

                $splat.SendTechnicianKeyIn = 'QueryString'
                $splat.SendInputDataIn = 'Body'
            }

            It 'Should invoke Invoke-RestMethod and Format-HttpQueryString once' {
                Invoke-GetRequest @splat
                Should -Invoke -CommandName Format-HttpQueryString -Times 1 -Exactly
                Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
                
            }
        }

        Describe 'TechnicianKey Body, InputData QueryString' {

            BeforeAll {
                
                Mock Format-HttpQueryString {
                    'https://test.com/api/?input_data={"list_info":{"row_count":"10"}}'
                }

                $splat.SendTechnicianKeyIn = 'Body'
                $splat.SendInputDataIn = 'QueryString'
            }

            It 'Should invoke Invoke-RestMethod and Format-HttpQueryString once' {
                Invoke-GetRequest @splat
                Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
                Should -Invoke -CommandName Format-HttpQueryString -Times 1 -Exactly
            }
        }

        Describe 'TechnicianKey Header, InputData Body' {

            BeforeAll {
                $splat.SendTechnicianKeyIn = 'Header'
                $splat.SendInputDataIn = 'Body'
            }

            It 'Should invoke Invoke-RestMethod once' {
                Invoke-GetRequest @splat
                Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
            }
        }

        Describe 'TechnicianKey QueryString, InputData Body' {

            BeforeAll {

                Mock Format-HttpQueryString {
                    'https://test.com/api/?TECHNICIAN_KEY=00000000-0000-0000-0000-000000000000'
                }

                $splat.SendTechnicianKeyIn = 'QueryString'
                $splat.SendInputDataIn = 'Body'
            }

            It 'Should invoke Invoke-RestMethod once' {
                Invoke-GetRequest @splat
                Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
                Should -Invoke -CommandName Format-HttpQueryString -Times 1 -Exactly
            }
        }

        Describe 'TechnicianKey Header, InputData Body Explicit' {

            BeforeAll {
                $splat.SendTechnicianKeyIn = 'Header'
                $splat.SendInputDataIn = 'Body'
            }

            It 'Should invoke Invoke-RestMethod once' {
                Invoke-GetRequest @splat
                Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
            }
        }

        Describe 'TechnicianKey Header, InputData Body Default' {
        
            BeforeAll {

                $splat.Remove('SendTechnicianKeyIn')
                $splat.Remove('SendInputDataIn')
            }

            It 'Should invoke Invoke-RestMethod once' {
                Invoke-GetRequest @splat
                Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly
            }
        }
    }       
}