12

I have the test class below in a .NET Core 1.1 Unit Test project (not an xUnit Test project) in Visual Studio 2017. How do I pass command line arguments to TestMethod?

[TestClass]
public class TestClass
{
    [TestMethod]
    public void TestMethod()
    {
        var args = Environment.GetCommandLineArgs();
        var json = JsonConvert.SerializeObject(args);
        throw new Exception(json);
    }
}

Lots of places on the internet make it sound like you can just put a -- in front of the arguments you want to pass in, but I can't get it to work.

These are the commands I've tried:

  • dotnet test TestProject.csproj -- hello=world
  • dotnet test TestProject.csproj -- --hello world
  • dotnet test TestProject.csproj -- -hello world

But all of them output this message every time. Note how neither hello nor world are present:

["C:\Users\____\.nuget\packages\microsoft.testplatform.testhost\15.0.0\lib\netstandard1.5\testhost.dll","--port","55032","--parentprocessid","24440"]

The first string is just the name of the running assembly -- pretty standard for the first command line argument. I don't know where the --port or --parentprocessid arguments are coming from.

Also, these variations make dotnet test choke with One or more runsettings provided contain invalid token (sic):

  • dotnet test TestProject.csproj -- -hello=world
  • dotnet test TestProject.csproj -- --hello=world

Edit: It looks like GetCommandLineArgs() isn't that great of an option, even if it did work here.

Also, the answer to this question on social.msdn.microsoft.com from 2006 says:

The way those classes are instantiated so VS can do unit testing against them is entirely different than normal execution. The executable is not run like normal, and neither you nor VS can provide arguments to it. The classes are instantiated indepdently of program execution by using the binary as a class library. (sic)

I wonder if this still applies to dotnet test?

In other news, this guy on SO was doubtful that command line arguments could be passed to DLLs at all, but he's wrong:

[Prototype for the <entrypoint> function:] void CALLBACK EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);

[Rundll] calls the <entrypoint> function, passing the command line tail which is the <optional arguments>.

Pang
  • 9,564
  • 146
  • 81
  • 122
Matt Thomas
  • 5,279
  • 4
  • 27
  • 59
  • By implication it smells like `dotnet test` is not suitable for running integration tests when you need to keep secrets/credentials out of code, which is what I was trying to get it to do – Matt Thomas Mar 24 '17 at 13:18

3 Answers3

4

This is one of the places I encountered while searching for this answer, so it's only fair I share my workaround here.

At this moment, there is no way to pass parameters to any .net test project. You could use the mstest testsettings/runsettings file, but that requires creating seperate files. In my case, I needed a servername and a password for my Selenium Coded UI tests, and I did not want to store them.

One of the recommended ways of passing parameters, is through environment variables. So in your C# xunit file, you can do something like:

// in mytest.cs
var user = Environment.GetEnvironmentVariable("TestUser");
var password = Environment.GetEnvironmentVariable("TestPassword");
var url = Environment.GetEnvironmentVariable("TestUrl");

If you do not want to definitively set your environment variables, remember you can always set them temporarily for just your session process. One way of doing this, is by creating a simple cmd file

#launchtests.cmd
SETLOCAL
SET TestUser='pete001'
SET TestPassword='secret'
SET TestUrl='http://testserver.local/login'
DOTNET TEST mytest.csproj

And now the fun part. You can parameterize every aspect of this. So you can change it to:

#run wity launchtests.cmd pete001 secret 'http://testserver.local/login'
SETLOCAL
SET %1
SET %2
SET %3
DOTNET TEST mytest.csproj

Or if you want to launch the test from an Azure DevOps (fka VSTS or TFS) pipeline, you can simply use the $(...) notation to inline variables, even if they're marked secret and/or come from Azure KeyVault.

#In AzureDevops, variables not marked as secret are already added to the environment
SET TestPassword=$(TestPassword)
dotnet test $(Build.SourcesDirectory)\MyCompany.MyProduct.UITests\MyTest.csproj --configuration $(BuildConfiguration) --collect "Code Coverage" --logger trx --results-directory $(Agent.TempDirectory)

run Azure DevOps command task

realbart
  • 3,497
  • 1
  • 25
  • 37
1

You can pass Runsettings to the test runner like this (example for bash):

dotnet test PathToYourTestProject.dll -- 'TestRunParameters.Parameter(name="YourTestProject.Args",value="ServiceConfig:BaseAddress=http://localhost:8080 ServiceConfig:MaxMemory=2048")'

For other shell types this is obviously a question of correct escaping which might be a pain as you will see. For MSTest the parameters are accessible via

var args = TestContext.Properties["YourTestProject.Args"].Split();
Paul Michalik
  • 4,331
  • 16
  • 18
0

I think I'm going about this all wrong. A blend of the below two things is probably the best approach.

Option 1: Refactor from integration tests into unit tests

TestMethod as it is now is an integration test, not a unit test, because it depends on command line arguments.

If I refactor TestMethod so that it gets the command line arguments from an interface like this:

interface IArgumentsProvider
{
    IEnumerable<string> GetArguments();
}

...then this transforms TestMethod into a unit test, where IArgumentsProvider can be mocked, so that only the functionality within TestMethod is tested (that's the definition of a unit test).

Note that I'd probably want to then move tests on the non-mocked implementation of that interface to where the other integration tests are. See below.

Option 2: Move integration tests out of Unit Test projects to elsewhere in deployment pipeline

It isn't always possible to refactor a test into a unit test. For example, at some point you're going to want to guarantee that a Location controller can actually post and retrieve geo-coordinates from a SQL database before releasing the code into the wild.

That's an example of an integration test. And integration tests belong elsewhere in the deployment pipeline: instead of having the test execute as part of the build definition within a Unit Test project, move it into the release definition and invoke it from a more proper framework.

Matt Thomas
  • 5,279
  • 4
  • 27
  • 59
  • 1
    I have unit tests as well as integration tests in my solution. 1 project for unit and another for integration. How do I pass variables in to my integration test project? I need to pass variables, only known at build time during CICD, in to the tests so it knows how/where to test. – Jerrod Horton Jan 29 '18 at 19:44
  • 1
    @JerrodHorton Assuming you mean you have two `dotnet test` projects, I've found that `dotnet test` is the wrong framework for integration tests. Instead, you might consider creating a separate console application which you can build and to which you can pass those build-time variables. Alternatively, I'm sure there are other great frameworks besides `dotnet test` meant specifically for integration tests. – Matt Thomas Jan 30 '18 at 01:48
  • 3
    Whether or not a .net project is the right thing to use, which is your opinion, the fact still remains that many people use them to perform integration tests. The question still remains, how do you pass data into a dotnet test project via command line? – Jerrod Horton Jan 31 '18 at 02:58
  • 1
    @JerrodHorton I don't want to sound like I'm arguing, but there is no way to pass data into a `dotnet test` project via command line. Therefore, it is unsuitable for those integration tests which require command line arguments. By consequence, one must conclude that `dotnet test` is not intended for integration testing, because it is impossible to implement that large category of integration tests. In my opinion, the more productive question is: what tools besides `dotnet test` exist for implementing integration tests? – Matt Thomas Jan 31 '18 at 15:14
  • @JerrodHorton For further reading, I recommend [this page from Microsoft](https://docs.microsoft.com/en-us/aspnet/core/testing/integration-testing). There, they talk about tools and techniques they have found useful for integration testing. And I'm not saying you specifically, but many people (myself included for the longest time) do not know the difference between a _unit_ test and an _integration_ test. So they don't catch the subtlety when [dotnet test's documentation](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=netcore2x) says it is "used to execute unit tests". – Matt Thomas Jan 31 '18 at 15:19