Developing a script in PowerShell, I require to call an external executable file(.exe). currently I am developing this script with a TDD approach, therefore I require to mock the called to this .exe file.
I try this :
Describe "Create-NewObject" {
Context "Create-Object" {
It "Runs" {
Mock '& "c:\temp\my.exe"' {return {$true}}
Create-Object| Should Be $true
}
}
}
I got this response:
Describing Create-NewObject
Context Create-Object
[-] Runs 574ms
CommandNotFoundException: Could not find Command & "C:\temp\my.exe"
at Validate-Command, C:\Program Files\WindowsPowerShell\Modules\Pester\Functions\Mock.ps1: line 801
at Mock, C:\Program Files\WindowsPowerShell\Modules\Pester\Functions\Mock.ps1: line 168
at <ScriptBlock>, C:\T\Create-NewObject.tests.ps1: line 13
Tests completed in 574ms
Passed: 0 Failed: 1 Skipped: 0 Pending: 0 Inconclusive: 0
Is there a way to mock this kind of calls without encapsulate them inside a function?
I found a way to mock the call to this executable files:
function Create-Object
{
$exp = '& "C:\temp\my.exe"'
Invoke-Expression -Command $exp
}
And the test with the mock should looks like:
Describe "Create-NewObject" {
Context "Create-Object" {
It "Runs" {
Mock Invoke-Expression {return {$true}} -ParameterFilter {($Command -eq '& "C:\temp\my.exe"')
Create-Object| Should Be $true
}
}
}
Yes, unfortunately, as of Pester 4.8.1:
you cannot mock external executables by their full paths (e.g, C:\Windows\System32\cmd.exe)
you can mock them by file name only (e.g., cmd), but beware that in older Pester versions the mock is only called for invocations that explicitly use the .exe extension (e.g., cmd.exe) - see this (obsolete) GitHub issue
Your own workaround is effective, but it involves Invoke-Expression, which is awkward; Invoke-Expression should generally be avoided
Here's a workaround that uses a helper function, Invoke-External, which wraps the invocation of external programs and, as a function, can itself be mocked, using a -ParameterFilter to filter by executable path:
In your code, define the Invoke-External function and then use it to make your call to c:\temp\my.exe:
# Helper function for invoking an external utility (executable).
# The raison d'ĂȘtre for this function is to allow
# calls to external executables via their *full paths* to be mocked in Pester.
function Invoke-External {
param(
[Parameter(Mandatory=$true)]
[string] $LiteralPath,
[Parameter(ValueFromRemainingArguments=$true)]
$PassThruArgs
)
& $LiteralPath $PassThruArgs
}
# Call c:\temp\my.exe via invoke-External
# Note that you may pass arguments to pass the executable as usual (none here):
Invoke-External c:\temp\my.exe
Then, to mock the call to c:\temp\my.exe in your Pester tests:
Mock Invoke-External -ParameterFilter { $LiteralPath -eq 'c:\temp\my.exe' } `
-MockWith { $true }
Note: If you only have one call to an external executable in your code, you can omit the -ParameterFilter argument.
I tried something this and seemed to work.
$PathToExe = 'C:\Windows\System32\wevtutil.exe'
New-Item -Path function: -Name $PathToExe -Value { ... }
Mock $PathToExe { ... }
Related
If i try to run the below command:
./target/release/node-template build-spec --chain staging > stagingSpec.json
facing the below error:
Error: Input("Error opening spec file: No such file or directory (os error 2)")
Is there any guide how to use that staging flag??
Here are the rustdocs for the command, and it's implementation in the node template - this looks for specific from your chain specification file based on what you pass that is configued. In the template at the time of writing, the template only has dev and "everything else" mode:
fn load_spec(&self, id: &str) -> Result<Box<dyn sc_service::ChainSpec>, String> {
Ok(match id {
"dev" => Box::new(chain_spec::development_config()?),
"" | "local" => Box::new(chain_spec::local_testnet_config()?),
path =>
Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?),
})
}
Thus you would need to specify another in/node/src/chainspec.rs and configure the /node/src/commnad.rs to use the correct one when called.
I have a hard time creating a pester for a specific Powershell function using invoke-command and having a $using variable on a script block. It would always return an error whenever i run my test. Sample function and test below:
Function:
Function Execute-Function {
.
.
.
$Name = "Computer_Name"
$ScriptBlock = {
Import-Module "Activedirectory"
Get-Computer -Name $Using:Name
}
Invoke-Command -Session $Session -ScriptBlock $ScriptBlock
}
Test:
Describe 'Execute-Function' {
.
.
.
.
mock Import-Module {} -verifiable
mock Get-Computer {} -verifiable
mock Invoke-Command {
param($Scriptblock)
. $Scriptblock
} -verifiable
$result = Execute-Function
it 'should call all verifiable mocks'{
Assert-verifiablemocks
}
}
Error of my test would return A using variable cannot be retrieved. A using variable can be used only with Invoke-Command.... I can not understand this error even though I mocked the Get-Computer to return nothing? or do I need to change how I mock Get-Computer for my test to pass?
Thank You in Advance
I'm not sure you can emulate the $using: scope with Pester. You can, however, utilize the pre-$using:-scope way of doing things:
Invoke-Command -ScriptBlock {
Param(
[Parameter(Position = 0)]
[String] $Name
)
<# ... #>
} -ArgumentList $Name
I have a PowerShell .ps1 file which contains functions at the top of the script followed by different commands calling these functions. I am using Pester to unit test my script file.
How do I mock a function that is within my PowerShell .ps1 script?
I have tried mocking the function, but I get an error saying "could not find command".
I have also tried adding an empty "dummy" function in the describe block. This doesn't give me the above error, but it is not mocking the function within the script correctly.
I have two files. One to hold the tests and another that holds the functions and calls to the functions. Below are two examples:
File1.ps1
Function Backup-Directory([switch]$IsError)
{
If($IsError)
{
Write-Error "Error"
Exit 1
}
}
Backup-Directory $true
File2.Tests.ps1
$here = (Split-Path -Parent $MyInvocation.MyCommand.Path) -replace '\\test', '\main'
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
$productionFile = "$here\$sut"
Describe "File1" {
Context "When the Back-Directory outputs an error." {
# Arrange
Mock Back-Directory { }
Mock Write-Error
# Act
& $productionFile
$hasSucceeded = $?
# Assert
It "Calls Backup-Directory" {
Assert-MockCalled Backup-Directory -Exactly 1 -ParameterFilter {
$IsError -eq $true
}
}
It "Writes error message." {
Assert-MockCalled Write-Error -Exactly 1 -ParameterFilter {
$Message -eq "Error"
}
}
It "Exits with an error." {
$hasSucceeded | Should be $false
}
}
}
I don't think this is possible. At least with your current implementation. I asked this same question a while back... Pester Issue 414
BUT you could split out that inner function into another script file in the same directory allowing you to unit test and mock it. You would just have to dot source the function in your main script file to be able to use it:
Main-Function.ps1:
# Main script
function Main-Function {
# if debugging, set moduleRoot to current directory
if ($MyInvocation.MyCommand.Path) {
$moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path
}else {
$moduleRoot = $PWD.Path
}
# dot source the inner function
. "$moduleRoot\Inner-Function.ps1"
Write-Output "This is the main script. Calling the inner function"
Inner-Function
Write-Output "Back in the main script"
}
Inner-Function.ps1:
function Inner-Function {
Write-Output "This is the inner function"
}
Main-Function.Tests.ps1:
$moduleRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
# Load Testing Function
. "$moduleRoot\Main-Function.ps1"
# Load Supporting Functions
. "$moduleRoot\Inner-Function.ps1"
Describe 'Main Script' {
Mock Inner-Function { return "This is a mocked function" }
It 'uses the mocked function' {
(Main-Function -match "This is a mocked function") | Should Be $true
}
}
This is a really nice approach because we can unit test the inner function and as the logic grows, adding tests to it is very easy (and can be done in isolation from the rest of the scripts/functions).
Inner-Functions.Tests.ps1:
$moduleRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
# Load Testing Function
. "$moduleRoot\Inner-Function.ps1"
Describe 'Inner Function' {
It 'outputs some text' {
Inner-Function | Should Be "This is the inner function"
}
}
There are two main points here...
Finding the dependent function location regardless of your current execution directory...
if ($MyInvocation.MyCommand.Path) {
$moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path
}else {
$moduleRoot = $PWD.Path
}
AND
Dot Sourcing depending functions in the main function as well as the unit test files... . "$moduleRoot\Inner-Function.ps1"
Split-Path -Path $MyInvocation.MyCommand.Path is $null in a user session, but will NOT be $null when invoked from within an execution context. Meaning, you could be in C:\users\nhudacin and still load this script/module correctly. Otherwise you would have to always execute this script/module from within the same directory as where it's located without using $MyInvoation variable.
I need to "Pester-test" 2 Azure cmdlets, Get-AzureNetworkSecurityGroup and Set-AzureNetworkSecurityRule from a PowerShell function inside a module, that looks like following:
$nsg = Get-AzureNetworkSecurityGroup -Name $NsgName
Set-AzureNetworkSecurityRule -Name $NsgRule.name `
-Type Outbound `
# ... other properties here ...
-NetworkSecurityGroup $nsg |
Format-List -Property Name,Location,Label
The parameters $NsgName, $NsgRule are not so important, the problem is I receive errors when mocking Set-AzureNetworkSecurityRule like:
Mock Get-AzureNetworkSecurityGroup { return [PSCustomObject] #{ Name='Any' } }
Mock Set-AzureNetworkSecurityRule
Mock Format-List
The error says:
[-] Error occurred in Describe block 100ms
PSInvalidCastException: Cannot convert the "#{Name=Any}" value of type "System.Management.Automation.PSCustomObject" to type "Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.INetworkSecurityGroup".
ArgumentTransformationMetadataException: Cannot convert the "#{Name=Any}" value of type "System.Management.Automation.PSCustomObject" to type "Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.INetworkSecurityGroup".
ParameterBindingArgumentTransformationException: Cannot process argument transformation on parameter 'NetworkSecurityGroup'. Cannot convert the "#{Name=Any}" value of type "System.Management.Automation.PSCustomObject" to type "Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.INetworkSecurityGroup".
It's quite clear what's happening, the issue is I don't know how to mock an object of type INetworkSecurityGroup. My expectation was initially to be no problem if mocking both Azure cmdlets.
I've also tried mocking Set-AzureNetworkSecurityRule using -MockWith:
Mock Set-AzureNetworkSecurityRule -MockWith {#{NetworkSecurityGroup='test-stuff'}}
with no luck.
Can anyone point me to the right direction ?
Thanks in advance
UPDATE with full Describe statement
First try
$environmentConfig = (Get-AzureEnvironmentConfig "Staging")
Describe 'Set-NetworkSecurityRuleFromObject' {
$role = ($environmentConfig.roles | Where { $_.role_name -eq 'Web'})
$nsgName = Get-NetworkSecurityGroupName -EnvironmentConfig $environmentConfig -Role $role
$rule = $role.nsg_rules.outbound[0]
Mock Get-AzureNetworkSecurityGroup
Mock Set-AzureNetworkSecurityRule
Set-NetworkSecurityRuleFromObject -NsgName -$nsgName -NsgRule $rule -NsgRuleType "Outbound"
It 'Should call mocked functions at least once' {
Assert-MockCalled Get-AzureNetworkSecurityGroup -Times 1 -Scope Describe
Assert-MockCalled Set-AzureNetworkSecurityRule -Times 1 -Scope Describe
}
}
Associated PS module function call:
Get-AzureNetworkSecurityGroup -Name $NsgName |
Set-AzureNetworkSecurityRule -Name $NsgRule.name `
-Type $NsgRuleType `
# More parameter initialization here ...
-Protocol $NsgRule.protocol |
Format-List -Property Name,Location,Label
Second try, another implementation of PS function that didn't work:
$nsg = Get-AzureNetworkSecurityGroup -Name $NsgName
$nsg |
Set-AzureNetworkSecurityRule -Name $NsgRule.name `
-Type $NsgRuleType `
# More parameter initialization here ...
-Protocol $NsgRule.protocol |
Format-List -Property Name,Location,Label
Third try
Describe 'Set-NetworkSecurityRuleFromObject' {
[ ... ]
Mock Get-AzureNetworkSecurityGroup { return [PSCustomObject] #{ Name='Any' } }
Mock Set-AzureNetworkSecurityRule
Set-NetworkSecurityRuleFromObject -NsgName -$nsgName -NsgRule $rule -NsgRuleType "Outbound"
It 'Should call mocked functions at least once' {
Assert-MockCalled Get-AzureNetworkSecurityGroup -Times 1 -Scope Describe
Assert-MockCalled Set-AzureNetworkSecurityRule -Times 1 -Scope Describe
}
}
This is still one of the most challenging things to do with Pester. You need to mock Get-AzureNetworkSecurityGroup in such a way that it returns a valid object to be passed to Set-AzureNetworkSecurityGroup later, and figuring out just how to do that can sometimes be tricky. In this specific case, it's not too bad:
Mock Get-AzureNetworkSecurityGroup {
return New-Object Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.SimpleNetworkSecurityGroup('Any', 'someLocation', 'someLabel')
}
Since this function parameter is an interface type, you could also write a quick class in C# (or in PowerShell v5) which implements that interface; sometimes this might be more desirable, if the actual class starts doing "stuff" as soon as you create an instance. However, this SimpleNetworkSecurityGroup class doesn't really do anything other than hold data, and it's safe to use as-is. (Verified with ILSpy.)
When i execute the plugins/SamplePlugin test cases, it executing perfect except the controller functions which are related to urls.
The test case function like
public function testIndex()
{
$this->get('/sample-plugin /mycontroller/index');
$this->assertResponseOk();
}
when i execute the above testcase the exception
There was 1 error:
1) SamplePlugin\Test\TestCase\Controller\MyControllerTest::test
Index
include(D:\xampp\htdocs\EATZ_V2_3.X\vendor\cakephp\cakephp\tests\test_app\config\routes.php): failed to open stream: No such file or directory
D:\xampp\htdocs\MyApp\vendor\cakephp\cakephp\src\Routing\Router.php:974
D:\xampp\htdocs\MyApp\vendor\cakephp\cakephp\src\Routing\Router.php:974
D:\xampp\htdocs\MyApp\vendor\cakephp\cakephp\src\Routing\Router.php:547
D:\xampp\htdocs\MyApp\vendor\cakephp\cakephp\src\TestSuite\IntegrationTestCase.php:451
D:\xampp\htdocs\MyApp\vendor\cakephp\cakephp\src\TestSuite\IntegrationTestCase.php:392
D:\xampp\htdocs\MyApp\vendor\cakephp\cakephp\src\TestSuite\IntegrationTestCase.php:312
D:\xampp\htdocs\MyApp\vendor\cakephp\cakephp\src\TestSuite\IntegrationTestCase.php:233
D:\xampp\htdocs\MyApp\plugins\SamplePlugin\tests\TestCase\Controller\MyControllerTest.php:29
D:\xampp\php\pear\PHPUnit\TextUI\Command.php:176
D:\xampp\php\pear\PHPUnit\TextUI\Command.php:129
FAILURES!
Tests: 30, Assertions: 42, Errors: 1.
please resove the issue.thanks in advance!!!
The error states that the routes.php file is missing in the folder config. Refer to the CakePHP 3 Documentation to create a meaningful routes.php.