Pester icon indicating copy to clipboard operation
Pester copied to clipboard

Support for Mocking Functions from Binary modules

Open AlexisColes opened this issue 6 years ago • 9 comments

We have a whole bunch of binary modules that are created from c# code that are also used in services and applications. We do not want to duplicate this code in powershell as it would be a waste of time. All our powershell script modules make use of these core functions in the binary modules.

But we cannot use Pester to test these script modules as we cannot Mock the calls to the core binary modules.

Please could you add support for this use case.

AlexisColes avatar Sep 18 '19 10:09 AlexisColes

Facing the same issue.

We are trying to mock some functions from AWSPowerShell.NetCore but functions there are binary type and can not be mocked. It would be great to see pester support this.

cuichenli avatar Oct 02 '19 01:10 cuichenli

This depends on multiple things. The .NET type system is strict and some things simply cannot be done, because it would compromise security. Could you show me some examples of what you are trying to do so I can research what is possible? I already have a pretty good idea about the limitations, but it's hard to describe without giving you concrete examples on your own problems.

nohwnd avatar Oct 02 '19 08:10 nohwnd

+1 Simple AWSPowerShell cmdlet calls are the basis of many of my cloud automation. If I am unable to test units wrapping AWSPowerShell calls and instead have to rewrite that code in bare API calls just to test my wrapping units I might as well just rewrite the entire module. I am sure many others can say the same about other providers of binary modules

I think this functionality is crucial and should become a major push in upcoming features. If I can help in any way please let me know!!!

tomer-ds avatar Mar 22 '20 06:03 tomer-ds

As per docs, Mocking public functions from a binary module works and is supported. But I think the consufion is caused by this clause:

However, the Mock and InModuleScope features can only be used for commands in Script modules due to limitations in the way that other module types are implemented in PowerShell.

This should rather say that injecting mocks is only suported in script modules. That is replacing internal functions in a module that are not public.

I tried to replicate it here, and I see what you probably see, that specifying the ModuleName will try to insert the functions into the binary module and will fail fast. This is a design error and will need to be revisited in v5. I think ModuleName should simply help us specify the module better (for private functions where we cannot resolve them from the public function), and not mean inject.

In v4 I can't do much about this without introducing a breaking change. But I will add a function name check here to see if the function is an exported function from a binary module and only report the error if not. And also improve the docs.

using System.Management.Automation;

namespace SomeModule
{
    [Cmdlet(VerbsCommon.Get, "Something")]
    public class GetSomethingCommand : PSCmdlet
    {
        [Parameter(Position = 0)]
        public string InputObject { get; set; }

        protected override void EndProcessing()
        {
            this.WriteObject(InputObject);
        }
    }
}
Import-Module "$PSScriptRoot\SomeModule.dll"

Describe "Get-Something" { 
    It "Is binary module" { 
        (Get-Module SomeModule).ModuleType | Should -Be Binary
    }

    It "just passes the value when not mocked" { 
        Get-Something "aaa" | Should -Be "aaa"
    }

    It "returns mock value when mocked" { 
        Mock Get-Something { "hello" }

        Get-Something "aaa" | Should -Be "hello"
    }

    It "throws when you try to inject into the module" { 
        # this is what you probably see, here we are trying to inject a mock into 
        # the SomeModule module and we fail-fast when we detect the module is a Binary module
        Mock Get-Something { "hello" } -ModuleName "SomeModule"

        Get-Something "aaa" | Should -Be "hello"
    }
}
Describing Get-Something
  [+] Is binary module 11ms
  [+] just passes the value when not mocked 11ms
  [+] returns mock value when mocked 34ms
  [-] throws when you try to inject into the module 82ms
    RuntimeException: Module 'SomeModule' is not a Script module.  Detected modules of the following types: 'Binary'
    at Get-ScriptModule, C:\Users\jajares\Documents\PowerShell\Modules\Pester\4.9.0\Functions\InModuleScope.ps1: line 124
    at Validate-Command, C:\Users\jajares\Documents\PowerShell\Modules\Pester\4.9.0\Functions\Mock.ps1: line 864
    at Mock, C:\Users\jajares\Documents\PowerShell\Modules\Pester\4.9.0\Functions\Mock.ps1: line 193
    at <ScriptBlock>, C:\Users\jajares\Dropbox\pester\binary-module-mocking.ps1: line 38

nohwnd avatar Mar 22 '20 10:03 nohwnd

Apologies, but I am confused. I nthe documentation at the bottom it very clearly states

Binary Modules

Commands that are exported from a Binary module can be tested with Pester, but the Mock and InModuleScope features cannot be used with Binary modules, and there are currently no workarounds.

Am I missing something?

tomer-ds avatar Mar 22 '20 18:03 tomer-ds

I think at this point the only thing left to do is test one abstraction up and mock my internal wrappers. This is an awful lot of trust to put in very central units without being able to control test cases at the lowest levels

tomer-ds avatar Mar 22 '20 18:03 tomer-ds

@tomerMP nah you are right, I thought that block says "but the Mock and InModuleScope features cannot be used within Binary modules".

So what is possible is to Mock a function that is publicly exposed from a module, so that the callers in the script will see the mock instead of that function. That works, as long as you don't provide the -ModuleName parameter to mock. And it works because we are shadowing the function within the current session state.

What is not possible, is to mock a function that is internal to the Binary module, or a function that is public but mocking the call to it inside of the Binary module.

Maybe show me an example of what you are trying to achieve (on a simple example if possible), and maybe we can come up with a way to do it together.

nohwnd avatar Mar 23 '20 06:03 nohwnd

Hi guys, just out of curiosity has there been any progress on this or could you add an example to the docu that showcases "Dos and Donts" regarding this comment? I think this would help people who stumble upon this issue:

So what is possible is to Mock a function that is publicly exposed from a module, so that the callers in the script will see the mock instead of that function. That works, as long as you don't provide the -ModuleName parameter to mock. And it works because we are shadowing the function within the current session state.

Anyway thanks for the detailed explanation at least I know how to bypass it right now. Cheers

Haratsu avatar Jan 15 '22 16:01 Haratsu

@Haratsu no progress from what you see here. Please go ahead and add the example to docs.

nohwnd avatar Jan 17 '22 08:01 nohwnd

What about Alpha?

On Sat, Mar 30, 2024 at 2:19 AM bravo-kernel @.***> wrote:

Closed #1367 https://github.com/pester/Pester/issues/1367 as completed via pester/docs#230 https://github.com/pester/docs/pull/230.

— Reply to this email directly, view it on GitHub https://github.com/pester/Pester/issues/1367#event-12300189455, or unsubscribe https://github.com/notifications/unsubscribe-auth/BA7FZAXCUVP5GPWQKC3E5ZLY2ZYP7AVCNFSM4IX5B2LKU5DIOJSWCZC7NNSXTWQAEJEXG43VMVCXMZLOORHG65DJMZUWGYLUNFXW4OZRGIZTAMBRHA4TINJV . You are receiving this because you are subscribed to this thread.Message ID: @.***>

billgothacked avatar Mar 30 '24 16:03 billgothacked