Reputation: 832
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.
Upvotes: 6
Views: 8660
Reputation: 449
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!
Upvotes: 1
Reputation: 15399
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.).
Upvotes: 0
Reputation: 1185
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();
Upvotes: 1
Reputation: 246998
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);
}
Upvotes: 11