Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to mock commands like azure cli #1883

Closed
dgroh opened this issue Mar 23, 2021 · 7 comments · Fixed by pester/docs#109
Closed

How to mock commands like azure cli #1883

dgroh opened this issue Mar 23, 2021 · 7 comments · Fixed by pester/docs#109

Comments

@dgroh
Copy link

dgroh commented Mar 23, 2021

I tried to find in the docs how to mock commands like Azure Cli

For instance I have the following code:

function NewAppRegistration($name, $replyUrls, $resourceAccessesFilePath, $adminConsent = $true) {   
    $appRegistration = az ad app create `
        --display-name $name `
        --reply-urls $replyUrls `
        --oauth2-allow-implicit-flow true `
        --available-to-other-tenants false `
        -o json | 
    ConvertFrom-Json

    if (Test-Path $resourceAccessesFilePath) {
        $appRegistration = az ad app update `
        --id $appRegistration.appId `
        --required-resource-accesses $resourceAccessesFilePath
    }

    if ($adminConsent) {
        az ad app permission admin-consent --id $appRegistration.appId
    }

    return $appRegistration
}

How can I mock az ad app create and assert the parameters with Assert-MockCalled? I don't find anything similar on the docs. Could you please update it with some example? Or should I assume this is not possible and I should create a wrapper for every Azure cli command? I don't want to rely on Azure AD Module

@fflaten
Copy link
Collaborator

fflaten commented Mar 23, 2021

The big difference is that your native commands won't have parameters, so you need to rely on the $args-array in filters etc. Sample:

Describe 'Mocking native commands' {
    It "Mock bash" {
        function GetBashVersion ($myParam) {
            & bash --version $myParam`
            --version
        }
        
        Mock bash {}

        GetBashVersion -myParam "foo bar"

        Should -Invoke -CommandName "bash" -Exactly -Times 1 -ParameterFilter { $args[0] -eq '--version' }
        # Using string concat (whitespace by default) you can match a pattern if order might change etc. Just remember linebreaks do count
        Should -Invoke -CommandName "bash" -Exactly -Times 1 -ParameterFilter { "$args" -match 'foo bar\s+--version' }
    }
}

This should ideally be moved to https://github.com/pester/docs as the example would probably be best suited in the usage-guide, https://pester.dev/docs/usage/mocking .

@dgroh dgroh changed the title How to mock comamnds like azure cli How to mock commands like azure cli Mar 23, 2021
@dgroh
Copy link
Author

dgroh commented Mar 24, 2021

Ok, that is exactly what I was looking for. Yes would be great to update the docs

@rpothin
Copy link

rpothin commented Apr 6, 2021

Hello,

I would like to mock Azure CLI commands too, but in my case, it would not be for an Assert-MockCalled.

You will find below more details regarding what I am trying to do.

  • Overview of my function
Function Fun-Ction($DisplayName) {
    $appList = az ad app list --filter "displayName eq '$DisplayName'" | ConvertFrom-Json
    $appListMeasure = $appList | Measure
    $appListCount = $appListMeasure.Count

    if($appListCount -eq 1) {
        ...
    }

    if($appListCount -eq 0) {
        ...
    }

    if($appListCount -gt 1) {
        ...
    }

    return $output
}
  • Overview of an example of test I would like to build using Pester
Context "Context" {
    It "Test" {
        Mock az {} # or something like that 😊

        Fun-Ction -DisplayName "foo" | Should -Be "Ok" # call to my function with a mock of the following command: az ad app list --filter "displayName eq '$DisplayName'"
    }
}

For example, I would like to validate the behavior of my function based on the number of items in the output of the call to the Azure CLI command.

Do you think there is today a way to do that with Pester?

@fflaten
Copy link
Collaborator

fflaten commented Apr 6, 2021

Yes. Just replace the empty {} in your mock-command to something that returns the right number of JSON-objects with the required properties so your function will output "OK".

Context "Context" {
    It "Test" {
        Mock az { ([pscustomobject]@{ name="foo" }) | ConvertTo-JSON }

        Fun-Ction -DisplayName "foo" | Should -Be "Ok"
}

If there are multiple calls to az in the function or It-block, you'd have to use a filter in the mock like Mock az { #some json-output } -ParameterFilter { "$args" -match 'ad app list' } (or logic inside mock code) to handle each call correctly.

@rpothin
Copy link

rpothin commented Apr 8, 2021

Hey @fflaten,

Thank you very much for your help.
It works like a charm 😊

@MladenMiljus
Copy link

MladenMiljus commented Apr 19, 2021

I sort of have a similar issue - but it's more to do with using Pester in Azure Pipelines (using v5.1.1 Pester).
That is I wanted to reference back to the OP's comment - I don't want to rely on Azure AD Module

I have this sort of mock for an Azure CLI command Get-AzSubscription -

$MockData = (Get-Content $PSScriptRoot\..\..\TestData.json) | ConvertFrom-Json
Mock Get-AzSubscription {return $MockData.Subscriptions} 
InModuleScope SubscriptionManagement {
    HasAccessToSubscription -SubscriptionId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx" | Should -Be $true
    HasAccessToSubscription -SubscriptionId "xxxxxxxx-xxxx-xxxx-yyyy-xxxxxxxxxxx" | Should -Be $false
}

Where Get-AzSubscription is used in HasAccessToSubscription.
And this works on my local machine, but of course, I do have the module Az.Accounts loaded - I haven't tried with deleting it locally.
But on Azure Pipelines, I just get the message:
CommandNotFoundException: Could not find Command Get-AzSubscription

To be able to get it to work, I have to import in my pipeline:
if ($modules.name -notcontains 'az.accounts') { Install-Module az.accounts -Scope CurrentUser -Force -SkipPublisherCheck }
Is that how I must understand the line from the document - Mocks the behavior of an existing command with an alternate implementation?

For any Azure command I want to mock and check - I have to install the module in my pipeline?

@fflaten
Copy link
Collaborator

fflaten commented Apr 19, 2021

This isn't related to the native cli mock issue. For future support questions see Where to get support 👍

The point of mocks is to imitate the existing method/function with the exact interface, but with controlled output and no external dependencies or danger/cost associated with executing the code. So mocks by-design require the original function to be available.

However, there's nothing stopping you from faking the original if it doesn't exist, but you'd have to provide your "original" with the expected parameters. Remember that at this point you are no longer testing against the public api/interface for Get-AzSubscription which may change in a future module-version - e.g. updated parameter-names

Describe 'Mock sample' {
    It 'Mocking unavailable function' {
        if($null -eq (Get-Command Get-AzSubscription -ErrorAction SilentlyContinue)) { function Get-AzSubscription ($SubscriptionId) {  } }
        Mock Get-AzSubscription { Write-Warning "Mock my world!" }
        function HasAccessToSubscription { Get-AzSubscription -SubscriptionId 123 }

        HasAccessToSubscription

        Assert-MockCalled -CommandName Get-AzSubscription -Times 1 -Exactly
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants