6

I have some functionality, which depends on command line arguments, and different arguments should lead to different results.

I can't directly "simulate" this arguments, since there are some sort of chain dependencies - I need to unit-test some xaml control, which depends on view-model, which depends on certain additional class, which fetches command line arguments using Environment.GetCommandLineArgs, and I can't directly impact on this last class to set arguments manually instead of using GetCommandLineArgs.

So, I'd like to know, is there any way to make Environment.GetCommandLineArgs return value I want it to return, for certain unit-test.

6
  • 1
    Can you post the code that you've tried so far? Commented Apr 14, 2016 at 14:49
  • Have you tried to use Moq? You can make any method return anything you want. Commented Apr 14, 2016 at 14:51
  • @Wjdavis5 I had bad experience trying to mock actual classes, not interfaces. But, some different solution came by, changing the logic i'm actually unit-testing to one, which can be applied moq to. Commented Apr 14, 2016 at 15:18
  • You need to abstract Environment.GetCommandLineArgs behind something you can mock Commented Apr 14, 2016 at 16:00
  • Are you saying you can't alter the code that accesses Environment.GetCommandLineArgs? Commented Apr 14, 2016 at 16:27

4 Answers 4

11

You need to abstract Environment.GetCommandLineArgs or what ever is eventually calling it behind something you can mock

public interface ICommandLineInterface {
    string[] GetCommandLineArgs();
}

Which can eventually be implemented in a concrete class like

public class CommandInterface : ICommandLineInterface {
    public string[] GetCommandLineArgs() {
        return Environment.GetCommandLineArgs();
    }
}

And can be Tested using Moq and FluentAssertions

[TestMethod]
public void Test_Should_Simulate_Command_Line_Argument() {
    // Arrange
    string[] expectedArgs = new[] { "Hello", "World", "Fake", "Args" };
    var mockedCLI = new Mock<ICommandLineInterface>();
    mockedCLI.Setup(m => m.GetCommandLineArgs()).Returns(expectedArgs);
    var target = mockedCLI.Object;

    // Act
    var args = target.GetCommandLineArgs();

    // Assert
    args.Should().NotBeNull();
    args.Should().ContainInOrder(expectedArgs);

}
Sign up to request clarification or add additional context in comments.

2 Comments

Very nice idea! +1
In the end, I did exactly this thing.
1

Since you are dealing with environment variables, why don't we wrap the outside dependencies into one EnvironmentHelper class, then inject the dependencies?

Here is my suggestion:

public class EnvironmentHelper
{
    Func<string[]> getEnvironmentCommandLineArgs; 

       // other dependency injections can be placed here

       public EnvironmentHelper(Func<string[]> getEnvironmentCommandLineArgs)
       {
            this.getEnvironmentCommandLineArgs = getEnvironmentCommandLineArgs;
       }

       public string[] GetEnvironmentCommandLineArgs()
       {
            return getEnvironmentCommandLineArgs();
       }
}

Here is the Mock method:

public static string[] GetFakeEnvironmentCommandLineArgs()
{
    return new string[] { "arg1", "arg2" };
}

In your source code:

EnvironmentHelper envHelper = new EnvironmentHelper(Environment.GetCommandLineArgs);
string[] myArgs = envHelper.GetEnvironmentCommandLineArgs();

In your unit test code:

EnvironmentHelper envHelper = new EnvironmentHelper(GetFakeEnvironmentCommandLineArgs);
string[] myArgs = envHelper.GetEnvironmentCommandLineArgs();

Comments

1

You can do it much more easier with Typemock Isolator. It allows to mock not only interfaces, so. Take a look:

[TestMethod, Isolated]
public void TestFakeArgs()
{
    //Arrange
    Isolate.WhenCalled(() => Environment.GetCommandLineArgs()).WillReturn(new[] { "Your", "Fake", "Args" });

    //Act
    string[] args = Environment.GetCommandLineArgs();

    //Assert
    Assert.AreEqual("Your", args[0]);
    Assert.AreEqual("Fake", args[0]);
    Assert.AreEqual("Args", args[0]);
}

Mocking Environment.GetCommandLineArgs() took only one line:

Isolate.WhenCalled(() => Environment.GetCommandLineArgs()).WillReturn(new[] { "Your", "Fake", "Args" });

And you don't need to create new Interfaces and to change production code.

Hope it helps!

4 Comments

"Mock Everything: 15 Days Trial"
Nice) But you know, some things are worth buying. It saved me a lot of money and time in sum
I wasn't trying to suggest it wasn't necessarily worth buying for some people (although if your MSDN supports Fakes, I believe that can accomplish the same thing - at least that feature). It was more for future readers of this question who don't necessarily have the means for whatever reason to invest in the licensing for a tool so they didn't hopefully waste their time clicking on the link. It might be worth incorporating that information into your answer for the benefit of future readers, as well.
0

If you want something unit-testable it should have its dependencies on a abstraction that is at least as strict as its implementation.

Usually you'd get the dependencies through your constructor of your class or a property method. Constructor is preferred, generally, because now a consumer of your class knows at compile-time what dependencies are needed.

public void int Main(string[] args)
{
    // Validate the args are valid (not shown).

    var config = new AppConfig();
    config.Value1 = args[0];
    config.Value2 = int.Parse(args[1]);
    // etc....
}

public class MyService()
{
    private AppConfig _config;

    public MyService(AppConfig config)
    {
        this._config = config;
    }
}

I normally don't put a config object behind an interface because it only has data - which is serializable. As long as it has no methods, then I shouldn't need to replace it with a subclass with override-d behavior. Also I can just new it up directly in my tests.

Also, I've never ran into a situation when I wanted to depend on an abstraction of the command line arguments themselves to a service - why does it need to know it's behind a command-line? The closest I've gotten is use PowerArgs for easy parsing, but I'll consume that right in Main. What I normally do is something like maybe read in the port number for a web server on the command-line arguments (I let the user of the app choose so that I can run multiple copies of my web server on the same machine - maybe different versions or so I can run automated tests while I'm debugging and not conflict ports), parse them directly in my Main class. Then in my web server I depend on the parsed command-line arguments, in this case an int. That way the fact that the configuration is coming from a command-line is irrelevant - I can move it to an App.config file later (which is also basically bound to the lifecycle of the process) if I prefer - then I can extract common configuration to configSource files.

Instead of depending on an abstraction for command-line in general (which each service consuming would have to re-parse if you kept it pure), I usually abstract the command-line and App.config dependencies to a strongly-typed object - maybe an app-level config class and a test-level config class and introduce multiple configuration objects as needed - (the app wouldn't necessarily care about this, while the E2E test infrastructure would need this in a separate part of the App.config: where do I grab the client static files from, where do I grab the build scripts in a test or developer environment to auto-generate/auto-update an index.html file, etc.).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.