vexx32 / PSKoans

A simple, fun, and interactive way to learn the PowerShell language through Pester unit testing.
GNU General Public License v3.0
1.72k stars 174 forks source link

AboutGetMember.Koans.ps1 does not return correct result #454

Closed DEberhardt closed 3 years ago

DEberhardt commented 3 years ago

Describe "Koan Bug, Issue, or Help Request"

Invoke-Koan : You must specify an object for the Get-Member cmdlet.
At C:\Users\David\Documents\WindowsPowerShell\Modules\PSKoans\0.67.1\Public\Get-Kar
ma.ps1:111 char:32
+                 $PesterTests = Invoke-Koan @{
+                                ~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (:) [Invoke-Koan], InvalidOperationExcep
   tion
    + FullyQualifiedErrorId : NoObjectInGetMember,Invoke-Koan

This is not caught.

Context "The Problematic Assertions"

It 'allows us to explore properties on an object'

        $Cmdlet1 = 'Get-History'
        $PropertyName = 'ModuleName'

        $Reason = $BecauseString -f $PropertyName, $cmdlet1
        **&** (Get-Command -Name $Cmdlet1) |
            Get-Member -MemberType Property -Name $PropertyName |
            Should -Not -BeNullOrEmpty -Because $Reason

This is throwing the error

Context "Your Attempts"

First, I thought this is because I was copying using a Parameter Name that doesn't exist (Typo, as always, my first idea; using Method as a property, or vice versa), but stepping through all corrections did not change this.

Next I tried to find issues with the Pester statement itself (the pipe is followed by a line feed without the back-tick). Removing the line feed did not solve the issue (I have learned something new then 😁 - no backtick needed after pipes^^)

Context "Additional Information"

managed to overcome this by looking closer at the code:

It 'allows us to explore properties on an object' {

        $BecauseString = "property '{0}' should be present in output from {1}"

        $Cmdlet1 = 'Get-History'
        $PropertyName = 'ModuleName'

        $Reason = $BecauseString -f $PropertyName, $cmdlet1
        #& (Get-Command -Name $Cmdlet1) | # Commented out to show the differences - just removed the "& " at the start. Parenthesis shouldn't be needed either if I am correct
        (Get-Command -Name $Cmdlet1) |
            Get-Member -MemberType Property -Name $PropertyName |
            Should -Not -BeNullOrEmpty -Because $Reason

}

This did result in the Error for the assertion to be removed.

This is still on PowerShell 5.1 / Windows Desktop / Pester 4.10.1

vexx32 commented 3 years ago

Check the results in your own shell for:

& (Get-Command Get-History)

vs

Get-Command Get-History

The exercise here is intended to illustrate the object types that are returned from the cmdlets as you actually execute them. The syntax here is a bit confusing perhaps because of the nature of the test, though.

Get-History will not be a great cmdlet for this purpose since PSKoans runs all tests in an isolated session, so Get-History will have nothing to show and will return no results. If you leave the test as it was and use (for example)Get-ChildItem in the test, you would get different results.

To illustrate, run these commands one after another:

Get-Command Get-ChildItem
& (Get-Command Get-ChildItem)

In other words, & is used to call the function that's being named as a string in the test; & (Get-Command Get-History) does the same thing as just calling Get-History.

A proper fix here would probably be to collect results first and verify the command produce some results, and give a specific error if it doesn't.

DEberhardt commented 3 years ago

I see. So, me choosing Get-History caused this to fail, not the syntax itself. Interesting. Have not considered the consequences of my actions (the output of the function^^). I knew & does call (or invoke?) what comes next, but I don't fully understand why it is synonymous with the second command (but not the first?) How does it know that? Following your Get-ChildItem example, I see the output is the same as if you were to run the command. Piping this to Get-Member will set it to find the Members of the Object coming through the pipeline. So far so good.

I understand that the Object received without the call operator is a different one:

❯ Get-History | Get-Member

   TypeName: Microsoft.PowerShell.Commands.HistoryInfo

❯ Get-Command Get-History | Get-Member

   TypeName: System.Management.Automation.CmdletInfo

❯ & (Get-Command Get-History) | Get-Member

   TypeName: Microsoft.PowerShell.Commands.HistoryInfo

If I do understand this correctly, i can mock run every single command and get the (default) output type it returns (Parameters changing the output notwithstanding)? - If so, this will be quite an epiphany!

Testing:

        $cmdlet1 = 'Get-ChildItem'
        $PropertyName = 'Directory'

        $Reason = $BecauseString -f $PropertyName, $cmdlet1
        $result = & (Get-Command -Name $Cmdlet1) | Get-Member -MemberType Property -Name $PropertyName
        $result | Should -Not -BeNullOrEmpty -Because $Reason

and

        $cmdlet1 = 'Get-History'
        $PropertyName = 'CommandLine'

        $Reason = $BecauseString -f $PropertyName, $cmdlet1
        $result = & (Get-Command -Name $Cmdlet1) | Get-Member -MemberType Property -Name $PropertyName
        $result | Should -Not -BeNullOrEmpty -Because $Reason

both work for me in the context of my shell (with Clear-History sprinkled in just before running the $result =..) Haven't tested it now with the full Koan though... - It might need a bit more testing :)

vexx32 commented 3 years ago

If I do understand this correctly, i can mock run every single command and get the (default) output type it returns (Parameters changing the output notwithstanding)? - If so, this will be quite an epiphany!

It's not really a mock run in the sense that if you do it with a destructive command, it can and will still do the things it's designed to do. But yeah, it's an alternative way of calling a command. 🙂

Get-Command returns one or more CommandInfo objects, and & has the ability to invoke those (among other things as well) which tells PS to call the command.

DEberhardt commented 3 years ago

ok. I think this was an unfortunate user error - closing