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 test ScriptBlock passed to mocked function #1042

Closed
fakhrulhilal opened this issue May 10, 2018 · 4 comments
Closed

How to test ScriptBlock passed to mocked function #1042

fakhrulhilal opened this issue May 10, 2018 · 4 comments

Comments

@fakhrulhilal
Copy link

I'm new to Pester, and I'd like to test my git cloner script. I create Invoke-VerboseCommand to ensure external command executed, either it throw error or not. Of course, it should show the executed command.

I'm using Windows 10, Pester 4.3.1, PowerShell 5.1

This is script that I'd like to test:

Function Invoke-VerboseCommand {
    param(
        [ScriptBlock]$Command,
        [string] $StderrPrefix = "",
        [int[]]$AllowedExitCodes = @(0)
    )
    $Script = $Command.ToString()
    $Captures = Select-String '\$(\w+)' -Input $Script -AllMatches
    ForEach ($Capture in $Captures.Matches) {
        $Variable = $Capture.Groups[1].Value
        $Value = Get-Variable -Name $Variable -ValueOnly
        $Script = $Script.Replace("`$$($Variable)", $Value)
    }
    Write-Host $Script
    If ($script:ErrorActionPreference -ne $null) {
        $backupErrorActionPreference = $script:ErrorActionPreference
    } ElseIf ($ErrorActionPreference -ne $null) {
        $backupErrorActionPreference = $ErrorActionPreference
    }
    $script:ErrorActionPreference = "Continue"
    try
    {
        & $Command 2>&1 | ForEach-Object -Process `
        {
            if ($_ -is [System.Management.Automation.ErrorRecord])
            {
                "$StderrPrefix$_"
            }
            else
            {
                "$_"
            }
        }
        if ($AllowedExitCodes -notcontains $LASTEXITCODE)
        {
            throw "Execution failed with exit code $LASTEXITCODE"
        }
    }
    finally
    {
        $script:ErrorActionPreference = $backupErrorActionPreference
    }
}

Function Invoke-GitCloneRepository {
    param(
        [string]$Uri,
        [string]$BranchTag,
        [string]$Path
    )

    Write-Host "Cloning $Uri for branch/tag '$BranchTag' into $Path"
    # try to embed authentication from system token for same TFS server
    If ((Test-SameTfsServer -Uri $Uri)) {
        $SystemToken = Get-EnvironmentVariable -Name 'SYSTEM_ACCESSTOKEN'
        $AuthHeader = "Authorization: bearer $SystemToken"
        Invoke-VerboseCommand -Command { git -c http.extraheader="$AuthHeader" clone --single-branch --progress -b $BranchTag "$Uri" "$Path" }
    }
    Else {
        Invoke-VerboseCommand -Command { git clone --single-branch --progress -b $BranchTag "$Uri" "$Path" }
    }
    If ($LastExitCode -ne 0) {
        Write-Error $output -ErrorAction Stop
    }
}

and here's my test

Describe 'Git clone' {
    Mock Invoke-VerboseCommand { Return $Command }
    It 'Does not use token when not in same VSTS server' {
        Mock Test-SameTfsServer { return $false }

        Invoke-GitCloneRepository -Uri 'http://dummy/repo.git' -BranchTag 'master' -Path TestDrive:\repo.git
        Assert-MockCalled Invoke-VerboseCommand -ParameterFilter { $Command -eq { git clone --single-branch --progress -b master 'http://dummy/repo.git' TestDrive:\repo.git } } # test failed
    }
    It 'Uses token when in same VSTS server' {
        Mock Test-SameTfsServer { return $true }
        Mock Get-EnvironmentVariable { return 'token' } -ParameterFilter { $Path -eq 'SYSTEM_ACCESSTOKEN' }
 
        Invoke-GitCloneRepository -Uri 'http://dummy/repo.git' -BranchTag 'master' -Path TestDrive:\repo.git
        Assert-MockCalled Invoke-VerboseCommand # test pass, but we don't test the passed command
   }
}

The goal is testing script block passed to Invoke-VerboseCommand function. How can I achieve this?

@nohwnd
Copy link
Member

nohwnd commented May 11, 2018

You are running still in the same scope so you can mock the git command and run the script as you normally would and then assert that git was called with the correct params. Like this:

Describe "-" {
    it "-" {
        # -- Arrange
        # at the moment we are not adding alias without .exe to executables
        # so your best bet is to create a function with name 'git' and mock that
        # other options is mock git.exe and call git.exe everywhere, but that is not
        # cross-platform
        function git () { }
        Mock git { }

        # -- Act
        # this is in your code
        & { git clone --single-branch --progress -b 1.0.0 "http://domain.com/abc.git" "here" }

        # -- Assert
        # you can use -ParameterFilter {  $true ; (Write-Host $args) }  to see the incoming value in console
        Assert-MockCalled git -ParameterFilter { 
            # make sure you put the string on the left side, $args is an array so it would not equal otherwise
            "clone --single-branch --progress -b 1.0.0 http://domain.com/abc.git here" -eq $args } 
    }
}

@fakhrulhilal
Copy link
Author

fakhrulhilal commented May 26, 2018

I change the test as follows:

Describe 'Git clone' {
	Function git {}
	Mock git { Write-Host $args }
    It 'Uses token when in same VSTS server' {
        Mock Test-SameTfsServer { return $true }
        Mock Get-EnvironmentVariable { return 'token' } -ParameterFilter { $Name -eq 'SYSTEM_ACCESSTOKEN' }
 
        Invoke-GitCloneRepository -Uri 'http://dummy/repo.git' -BranchTag 'master' -Path TestDrive:\repo.git
		Assert-MockCalled git -ParameterFilter { $args -icontains '-c' } # test passes
		Assert-MockCalled git -ParameterFilter { $args -icontains '-c http.extraheader' } # test fails
   }
}

and here's the result:

  Describing Git clone
Cloning http://dummy/repo.git for branch/tag 'master' into TestDrive:\repo.git
 git -c http.extraheader="Authorization: bearer token" clone --single-branch --progress -b master "http://dummy/repo.git" "TestDrive:\repo.git"
-c http.extraheader=Authorization: bearer token clone --single-branch --progress -b master http://dummy/repo.git TestDrive:\repo.git
    [-] Uses token when in same VSTS server 133ms
      Expected git to be called at least 1 times but was called 0 times
      46:               Assert-MockCalled git -ParameterFilter { $args -icontains '-c http.extraheader' }
      at <ScriptBlock>, D:\Dokumen\Proyek\TFSGitDownloader\tests\GitCommand.Tests.ps1: line 46

FYI, I don't change the implementation at all. It looks like $args only contains -c when tested, but all of string when printed. Did I miss something?

@fakhrulhilal
Copy link
Author

Finally I can get it works using your example. I'm new to PowerShell, I don't understand why when $args is in left it will failed but not when written in right. I wish you could tell me why. This issue can be closed anyway.

Describe 'Git clone' {
    Function git () {}
    Mock git { }
    Mock Write-Host { }
    It 'Uses token when in same VSTS server' {
        Mock Test-SameTfsServer { return $true }
        Mock Get-EnvironmentVariable { return 'token' } -ParameterFilter { $Name -eq 'SYSTEM_ACCESSTOKEN' }
 
        Invoke-GitCloneRepository -Uri 'http://dummy/repo.git' -BranchTag 'master' -Path TestDrive:\repo.git
        Assert-MockCalled git -ParameterFilter { '-c http.extraheader=Authorization: bearer token clone --single-branch --progress -b master http://dummy/repo.git TestDrive:\repo.git' -eq $args } # passes
        Assert-MockCalled git -ParameterFilter { $args -eq '-c http.extraheader=Authorization: bearer token clone --single-branch --progress -b master http://dummy/repo.git TestDrive:\repo.git' } # failed
    }

@fakhrulhilal
Copy link
Author

My question has answer. I'll close this issue.

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

No branches or pull requests

2 participants