How to unit test os/exec's cmd.Run() without actually invoking the program?
I'm writing a CLI and one of my desired features is to have a command called "feedback" that opens a browser window to the project's issue page using the appropriate helper program for the platform (xdg-open on Linux, open on Mac OS, etc.).
I've been able to isolate the building of the command to a func (getFeedback) that takes GOOS and my own command interface, allowing me to mock out the run() command for that particular unit test. But the parent func Feedback() that calls getFeedback() obviously has to call the real os/exec command, and os/exec doesn't seem to offer any helpful interfaces for mocking this.
I'm having issues figuring out how to write a unit test for the execution of cmd.Run that won't actually cause the process to run, since I obviously don't want a browser window to open when I run my tests.
I've found some suggestions on how to mock the real Run() (such as here), but they seem to be focused on running the external program and making sure some output is returned. In this case I don't care what is returned from the process, only whether there's an error running it.
Beyond that, it'd be nice if I could trap the exec() of the external process in some platform-agnostic way, make sure the command and args are what I expect, and prevent the external process from actually being run. That may be possible with this library, but I'm not sure that's the best design either. While my code is written for Linux and Mac OS today, I'd like to be able to extend it to Windows in the future.
Gist here: https://gist.github.com/brandentimm/0fd788e1a527eb7b74d090a01c72ae3d
Feedback() is the parent function that will be invoked by the CLI interface which I can't currently figure out how to validate or prevent from opening a browser window.
Any suggestions or wholesale critiques of my design welcome. Cheers!
EDIT: I should also add that the proposed solution from my first link introduces package-global state (var execCommand = exec.Command) which I'd like to avoid if possible.
Posted Oct 6, 2019, 10:42 PM UTC · edited
I'd make an interface for an invokable process. Write your implementation to that interface, use a mock implementation in your test. Seems you're mostly doing this already.
Mocks are for your architecture boundaries. You don't want to test the OS invoking some process. You want to test that you're calling that process correctly. Assert the correct args and what not are being sent to your public interface and set up different return values and most importantly errors.
Yea that's what I'm trying to achieve, you and I seem to agree on what a unit test should be. But I'm struggling with how best to implement it and not finding many examples available. I'm a bit surprised this isn't a more common pattern.