What to test?

I’ve written some Pester tests before for a couple of different modules that I have authored or helped author.
In those cases the tests seemed to come more naturally, “get a collection of objects. The collection should have this many records, and these properties” “Add a record to the collection. The collection should have one more record than before.”

I’ve just finished a fairly simple function that updates the “Location” property of printer objects via CIM. Because it has the potential to touch every printer object on a server, it has been suggested that I write a set of Pester tests for this function to help demonstrate that the function is ‘safe’ and ‘only does what I claim it does’.

It uses a couple of Get-CIMInstance calls, does a little string manipulation, and then does a set-CIMInstance to push the change.

I don’t really feel obligated to test whether Get-CimInstance works properly, though somehow, mocking the Set-CIMInstance seems somewhat disingenuous.

It seems like the key points are “Did I do the string manipulation correctly?”, and “Is the Location property the ONLY thing being changed?” The latter seems like the harder one to test. Sure, Set-CIMInstance’s Passthru switch gives us an object we can compare with our original, but that means we still have to make a change to a real printer object somewhere. Is there a way around that?

I’m still Google searching, and reading, so if I just haven’t found the right article yet, please don’t stomp on me too hard.

Mocking the “Gets”, and returning reasonable facsimiles of the real objects isn’t out of the question, so being able to test the string manipulation for the new Location label seems feasible.

You’ll want to -never- be the first to reply to your own post; it removes you from the “Unanswered Posts” list a lot of us use to find… unanswered posts :).

Pester isn’t designed to show that a function is ‘safe’ and ‘only does what I claim it does’. Pester is, first and foremost, about unit testing. Something can unit test perfectly and still blow up the world. I mean, if you wrote a function that shuts down every server on the network, and you wrote it well, then Pester would “pass” whatever tests you wrote, but it’s not necessarily “safe.”

I don't really feel obligated to test whether Get-CimInstance works properly, though somehow, mocking the Set-CIMInstance seems somewhat disingenuous.

You shouldn’t feel obligated; it’s not your code. Microsoft unit-tested Get-CimInstance already. Mocking it isn’t disingenuous; it’s the right thing to do. It takes “someone else’s code” out of the loop. Your unit test should be making sure that it’s only running against whatever computers you specified. That makes your unit test pretty simplistic.

Pester can be used for infrastructure validation, which is entirely different than unit testing. You’d let the CIM calls happen, and then “reach out” in a Pester test to validate that the change was, in fact, made. This is not nondestructive, though, and you need to code your tests to run against test targets, obviously, not production.

It seems like the key points are "Did I do the string manipulation correctly?", and "Is the Location property the ONLY thing being changed?" The latter seems like the harder one to test.

Indeed, although the latter is not hard. You’re not testing to make sure Microsoft’s command isn’t maliciously touching things you didn’t specify. You mock Set-CimInstance, and have it return just the name of the computer that was input to it. You test to make sure that happens. That’s proving -your- code is feeding a computer name and that it’s getting “to” Set-CimInstance properly; after that, it’s not your problem. If Set-CimInstance is broke, you file a bug on it on GitHub.

Gotta remember to Edit my posts, instead of replying, when I have more thoughts on what I’d previously written.

Thank you for the insight, Don!

Maybe part of the problem I’m facing here is that I have some parties who seem to get nervous about a thing being done ‘by a script’, and yes, I know how counter that it is to our whole… Idiom. So this morning, while you have given me the ideas I need to unit test this script, I’m also left wondering how useful it really is to essentially throw more code at the problem. I know it’s the right thing to do now, because it will be the right thing to do in the future. But will it satisfy the scripting ‘Luddites’ right now. It probably doesn’t matter.

Yeah, there’s no way to fix fear other than experience.

Set-ciminstance is a particularly onerous function because of its demand for a strictly typed input. Issue 601 on pester’s GitHub repository talks about this situation and one respondent provides a suggestion that I have found does work. That solution is to store a replica of a real world example of the object then deserialize it and use it as an input object for Set-ciminstance.

This is a common use case for a unit test. I would create a mock for Get-CimInstance making it return all of the interesting properties you know that Get-CimInstance always returns. Also, create a mock for Set-CimInstance that returns nothing (I think that’s what it does).

Once you have them both mocked, you can run your script all day without a problem. Be sure to use Assert-MockCalled on your Set-CimInstance mock and use a parameter filter specifying $PSBoundParameters.Location. From that, you’ll be able to verify what would have gotten passed to Set-CimInstance based on the input you provide.