nunit test working directory - c#

I have the following code (sample1.evol - file attached to my unit test project):
[Test]
public void LexicalTest1()
{
var codePath = Path.GetFullPath(#"\EvolutionSamples\sample1.evol");
//.....
}
I found that the working directory of test execution is not the assembly directory: (in my case codepath variable assigned to d:\EvolutionSamples\sample1.evol).
So, how can I change the execution working directory (without hardcode)? What will be the best practice to load any files attached to test case?

You can use following to get the directory of assembly running the code something like
var AssemblyDirectory = TestContext.CurrentContext.TestDirectory

I use this for integration tests that need to access data files.
On any machine the test needs to run create a system environment variable named TestDataDirectory that points to the root of where your test data is.
Then have a static method that gets the file path for you..
public static class TestHelper
{
const string EnvironmentVariable = "TestDataDirectory";
static string testDataDir = Environment.GetEnvironmentVariable(EnvironmentVariable);
public static string GetTestFile(string partialPath)
{
return Path.Combine(testDataDir, partialPath);
}
}
...
[Test]
public void LexicalTest1()
{
var codePath = TestHelper.GetTestFile(#"\EvolutionSamples\sample1.evol");
//.....
}

I am using this code:
var str = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
if (str.StartsWith(#"file:\")){
str = str.Substring(6);
}
Getting in str variable the assembly directory.

We were having a problem where tests run using ReSharper and NCrunch would work, but the native VS Test Runner would not be able to find the files, when given just a relative file path for the test to use. I solved it by creating a function that you pass the relative test file path into, and it will give you the absolute file path.
private static string _basePath = Path.GetDirectoryName(typeof(NameOfYourTestClassGoesHere).Assembly.Location);
private string GetAbsoluteTestFilePath(string relativePath) => Path.Combine(_basePath, relativePath);
You would then use the function like so:
var input = File.ReadAllLines(GetAbsoluteTestFilePath(#"TestData/YourTestDataFile.txt"));

Related

Good practice for DeploymentItem?

I'm fairly rusty when it comes to C#. I've been poking my nose around internet trying to find a solution to my question without success.
I created a test project using MSTest. Some tests use files, that I added to my project test under the folder TestData, and they are copied when executing the test by using the attribute DeploymentItem.
Example: [DeploymentItem(#"TestData\test.txt")]
This copies test.txt at the execution folder and it works. However, when I want to use this file in the test, I then have to work on "test.txt" instead of #"TestData\test.txt". Thus, if I want to factorize my code, I have to have two variables:
const string testFileName = "test.txt";
const string testFilePath = #"TestData\test.txt";
and then use them as
[DeploymentItem(testFilePath)]
public void TestFunction()
{
[...]testFileName[...]
}
Ideally, I want instead to write:
[DeploymentItem(testFilePath)]
public void TestFunction()
{
[...]testFilePath[...]
}
This way I would only need one variable.
It would work if I use the second argument of DeploymentItem as such:
const string testFilesFolder = "TestData";
const string testFilePath = #"TestData\test.txt";
[DeploymentItem(testFilePath, testFilesFolder)]
public void TestFunction()
{
[...]testFilePath[...]
}
However, that forces me and everyone to think about passing the second argument every time we use DeploymentItem. But it has the merit of working.
Here are the different things I tried to do to address the issue:
Inheriting from DeploymentItem to simply add my own constructor: DeploymentItem is sealed so this is not possible.
Creating my own attribute, by copying the code of DeploymentItem. The file is not copied at all:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
class DeployFileAttribute : Attribute
{
public DeployFileAttribute(string path)
{
Path = path;
OutputDirectory = System.IO.Path.GetDirectoryName(path);
}
public string Path { get; }
public string OutputDirectory { get; }
}
[DeployFile(testFilePath)] // testFilePath is not copied at all, even though the constructor is correctly executed.
Creating a method that would return the attribute. It does not seem like it is possible to use the result of a method as an attribute:
public static DeploymentItemAttribute DeployFile(string path)
{
return new DeploymentItemAttribute(path, System.IO.Path.GetDirectoryName(path));
} // No compilation error
[DeployFile(testFilePath)] // DeployFileAttribute type does not exist
Creating something like a C++ style using statement or C style macro, I can't seem to find a syntax that works
using DeployFile(string toto) = DeploymentItemAttribute(toto, System.IO.Path.GetDirectoryName(path)); // Syntax is wrong, could not find one that works
Any hindsight would be welcome!
From my point of view, there are only two possibilities:
You use DeploymentItem in the way it was created by Microsoft.
[DeploymentItem(testFilePath, testFilesFolder)] as you manshioned in your post
You can combine source path:
const string testFileName = "test.txt";
[DeploymentItem(#"TestData\" + testFileName)]
public void TestFunction()
{
[...]testFileName[...]
}
In this case, you'll have just one variable :)
You can write your own extension for MSTest and create an attribute you need. But this is not the easy way. As key words for this approach, you could google for TestExtensionExecution, ITestMethodInvoker and TestClassExtensionAttribute
On the other hand, this is very understandable, why DeploymentItem is implemented as it is. Do not forget, that the source folder can be an absolute path as well. So assume, that you have the following attribute [DeploymentItem(#"S:\Shared\TestFiles\AAA\BBB\test.txt")] What should be the destination folder? But even with relative paths: [DeploymentItem(#"..\..\..\TestFiles\AAA\BBB\test.txt")] - can say the name of the destination folder in this case?

Using log4net With Visual Studio Debugger

I'm having trouble getting my log4net.config file to load when using Visual Studio in debug mode for an Excel VSTO Plugin. The config file is in the top level directory of my project. I have the property "Copy to Output Directory" set to "Copy Always". This ensures the file is copied to bin/Debug/log4net.config. I can verify this is the case when I build.
However, the file won't load when I run in Debug mode. I gave up on trying to get the file to load automatically and decided to do it by code, as per the OP's code at the bottom of this question.
However, I realised that I needed to use an absolute path to the config file, as relative paths weren't picking it up. On further investigation, I realised that the executing DLL wasn't actually the DLL in the debug/bin folder. It was in the following location:
C:\Users\cbhandal\AppData\Local\assembly\dl3\MO52QQWP.9ZL\K36XZHGN.1PB\230751e6\d09b7fb2_19f6d401
Also the current working directory, as found by System.IO.Directory.GetCurrentDirectory(); was set to "C:\\Users\\cbhandal\\Documents".
Hard-coding the path as an absolute path works as in the following code:
var log4netConfig = "C:\\" + path + "\\Log4net.config";
var log4netInfo = new FileInfo(log4netConfig);
log4net.Config.XmlConfigurator.ConfigureAndWatch(log4netInfo);
But that's not a solution I can deploy. I'm stuck here. Wondering if there's a way to either force Visual studio to copy the .config file to that appdata/temp location, or if there's a way to programatically reference the folder where the original DLL lay- the one that was built. Or if anyone had any other solution?
For me the easiest solution was to use this:
https://stackoverflow.com/a/6963420/4754981
But there are several other solutions on that link for different approaches, each with their caveats.
So mine looks like this:
using System.Reflection;
using System.IO;
using System;
public static class Extensions {
private static string GetDirectory(this Assembly a) {
string codeBase = a.CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
private static void AlterLogPath(this log4net.Repository.ILoggerRepository repo, string newPath, string directory="") {
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy) repo;
foreach (log4net.Appender.IAppender a in h.Root.Appenders) {
if (a is log4net.Appender.FileAppender) {
var fa = (log4net.Appender.FileAppender)a;
var fileName = Path.GetFileName(fa.File);
fa.File = newPath + (String.IsNullOrEmpty(directory)?"":(directory + Path.DirectorySeparatorChar.ToString())); // edit: filename is attached after next line automatically.
fa.ActivateOptions();
break;
}
}
}
}
and in the bootup (via [assembly: System.Web.PreApplicationStartMethod] or otherwise for asp), or main app..
static void Main() {
var PATH = Assembly.GetExecutingAssembly().GetDirectory() + Path.DirectorySeparatorChar.ToString();
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(PATH + "log4net.config"));
log4net.LogManager.GetRepository().AlterLogPath(PATH, "Logs");
}

Teamcity - writing tests that access a separate JSON file

I have TeamCity running for a C# project. The Unit tests are written using MSTest and they include an external JSON file. They are loaded in because they're large and I don't want to have to escape them in C#.
I import them like this:
[TestInitialize]
public void Setup()
{
using (StreamReader r = new StreamReader(#".\currency2.json"))
{
_json = r.ReadToEnd();
}
...
They run fine locally. I have 'Copy always set' but when the tests are ran using Teamcity I get an error saying that it can't find them in a temp folder. They are copied over to the build server but they're not in this temp folder.
Could not find file 'E:\TeamCity\buildAgent\temp\buildTmp\SYSTEM_SERVER 2016-07-18 15_28_19\Out\currency2.json'
I have **\bin\release\*test*.dll setup as my Test File Names in the test build step.
Any help appreciated.
I had a similar problem.
I changed the properties of the test file to this
Build Action = Content
Copy to Output Directory = Copy always
Teamcity will copy the file to the build folder, but it does not seem to maintain the same structure you'd expect.
So I created a file lookup loop. That will step down the expected folder until it finds the text file in question.
var pathLookups = new string[]
{
"2ndFolder\\3rdFolder\\test.json", // folder that normally workes
"3rdFolder\\test.json",
"test.json"
};
foreach (var pathLookup in pathLookups)
{
try
{
jsonFileCollection = JsonFileLoader<TestJsonType>.LoadJson(pathLooksup);
if (jsonFileCollection!= null)
{
break;
}
}
catch (Exception)
{
Console.WriteLine("Attempted to load test json from path:" + pathLooksup);
}
}
It's not the cleanest solution, but it will get the job done. You could refactor this to look a little nicer.
You might pass the full pass by argument to your program (and value defined in TeamCity).
Something like this (this is a pseudo-code example only) :
string[] programArgs;
static void Main(string[] args)
{
programArgs = args
}
[TestInitialize]
public void Setup()
{
using (StreamReader r = new StreamReader(programArgs[1]))
{
_json = r.ReadToEnd();
}
...
}

Why AppDomain.CurrentDomain.BaseDirectory not contains "bin" in asp.net app?

I have a web project like:
namespace Web
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
lbResult.Text = PathTest.GetBasePath();
}
}
}
The method PathTest.GetBasePath() is defined in another Project like:
namespace TestProject
{
public class PathTest
{
public static string GetBasePath()
{
return AppDomain.CurrentDomain.BaseDirectory;
}
}
}
Why it's display ...\Web\ while the TestProject assembly is compiled into bin folder(in other words it should display ...\Web\bin in my thought).
Now I got a troublesome if I modified method into:
namespace TestProject
{
public class FileReader
{
private const string m_filePath = #"\File.config";
public static string Read()
{
FileStream fs = null;
fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + m_filePath,FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(fs);
return reader.ReadToEnd();
}
}
}
The File.config is created in TestProject. Now AppDomain.CurrentDomain.BaseDirectory + m_filePath will returen ..\Web\File.config (actually the file was be copied into ..\Web\bin\File.config), an exception will be thrown.
You could say that I should modified m_filePath to #"\bin\File.config". However If I use this method in a Console app in your suggest, AppDomain.CurrentDomain.BaseDirectory + m_filePath will return ..\Console\bin\Debug\bin\File.config (actually the file was copyed into .\Console\bin\Debug\File.config), an exception will be thrown due to surplus bin.
In other words, in web app, AppDomain.CurrentDomain.BaseDirectory is a different path where file be copyed into (lack of /bin), but in console app it's the same one path.
Any one can help me?
Per MSDN, an App Domain "Represents an application domain, which is an isolated environment where applications execute." When you think about an ASP.Net application the root where the app resides is not the bin folder. It is totally possible, and in some cases reasonable, to have no files in your bin folder, and possibly no bin folder at all. Since AppDomain.CurrentDomain refers to the same object regardless of whether you call the code from code behind or from a dll in the bin folder you will end up with the root path to the web site.
When I've written code designed to run under both asp.net and windows apps usually I create a property that looks something like this:
public static string GetBasePath()
{
if(System.Web.HttpContext.Current == null) return AppDomain.CurrentDomain.BaseDirectory;
else return Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"bin");
}
Another (untested) option would be to use:
public static string GetBasePath()
{
return System.Reflection.Assembly.GetExecutingAssembly().Location;
}
In case you want a solution that works for WinForms and Web Apps:
public string ApplicationPath
{
get
{
if (String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
{
//exe folder for WinForms, Consoles, Windows Services
return AppDomain.CurrentDomain.BaseDirectory;
}
else
{
//bin folder for Web Apps
return AppDomain.CurrentDomain.RelativeSearchPath;
}
}
}
The above code snippet is for binaries locations.
The AppDomain.CurrentDomain.BaseDirectory is still a valid path for Web Apps, it's just the root folder where the web.config and Global.asax are, and is same as Server.MapPath(#"~\");
If you use AppDomain.CurrentDomain.SetupInformation.PrivateBinPath instead of BaseDirectory, then you should get the correct path.
When ASP.net builds your site it outputs build assemblies in its special place for them. So getting path in that way is strange.
For asp.net hosted applications you can use:
string path = HttpContext.Current.Server.MapPath("~/App_Data/somedata.xml");

How to MapPath in a unit test in C#

I want to load an external XML file in a unit test to test some processing code on that XML. How do I get the path of the file?
Usually in a web app I would do:
XDocument.Load(Server.MapPath("/myFile.xml"));
But obviously in my unit test I have no reference to Server or HttpContext so how can I map a path so that I don't have to specify the full path?
UPDATE:
I just want to make it clear that the code I'm actually testing is for an XML parser class, something like:
public static class CustomerXmlParser {
public static Customer ParseXml(XDocument xdoc) {
//...
}
}
So to test this I need to parse a valid XDocument. The method being tested does not access the file system itself. I could create the XDocument from a String directly in the test code but I thought it would be easier to just load it from a file.
Another idea would be to utilize dependency injection.
public interface IPathMapper {
string MapPath(string relativePath);
}
And then simply use 2 implementations
public class ServerPathMapper : IPathMapper {
public string MapPath(string relativePath){
return HttpContext.Current.Server.MapPath(relativePath);
}
}
And then you also need your mock implementation
public class DummyPathMapper : IPathMapper {
public string MapPath(string relativePath){
return "C:/Basedir/" + relativePath;
}
}
And then all your functions that needs to map path's would simply need to have access to an instance of IPathMapper - in your web app it needs to be the ServerPathMapper and in your unit tests the DummyPathMapper - basic DI (Dependency Injection).
Personally, I'd be very wary about having any code that relies on a back-end
resource store, be that a file system or a database - you are introducing a dependency into your unit test that is likely to lead to false negatives i.e tests failing not because of your specific test code but because the file isn't there or the server is unavailable etc.
See this link for IMO a good definition of what a unit test is and more importantly is not
Your unit test should be testing an atomic, well-defined piece of functionality not testing whether a file can load.
One solution is to 'mock' the file load - there are various approaches to this however, I'd personally only mock the interface to the file system your are using and not try and do any full filesystem mocking - here's a good SO post and here's a good SO discussion on file system mocking
Hope that helps
Usually for unit tests I add the xml files as embedded resources to the project and load them using a method like this:
public static string LoadResource(string name)
{
Type thisType = MethodBase.GetCurrentMethod().DeclaringType;
string fullName = thisType.Namespace + "." + name + ".xml";
using (Stream stream = thisType.Module.Assembly.GetManifestResourceStream(fullName))
{
if(stream==null)
{
throw new ArgumentException("Resource "+name+" not found.");
}
StreamReader sr = new StreamReader(stream);
return sr.ReadToEnd();
}
}
Edit: I'm starting from scratch since I guess I interpreted your question the wrong way initially.
The best way to load an XML file in your unit test for injecting it then to some of your classes is to use the DeploymentItem attribute in MS unit tests.
This will look like the following:
[TestMethod]
[DeploymentItem(#"DataXmlFiles\MyTestFile.xml", "DataFiles")]
public void LoadXMLFileTest()
{
//instead of "object" use your returning type (i.e. string, XDocument or whatever)
//LoadXmlFile could be a method in the unit test that actually loads an XML file from the File system
object myLoadedFile = LoadXmlFile(Path.Combine(TestContext.TestDeploymentDir, "DataFiles\\MyTestFile.xml"));
//do some unit test assertions to verify the outcome
}
I didn't test the code now on a debugger, but it should work.
Edit:
Btw, when you use DeploymentItem consider this post here.
Classes:
internal class FakeHttpContext : HttpContextBase
{
public override HttpRequestBase Request { get { return new FakeHttpRequest(); } }
}
internal class FakeHttpRequest : HttpRequestBase
{
public override string MapPath(string virtualPath)
{
return /* your mock */
}
}
Usage:
[TestMethod]
public void TestMethod()
{
var context = new FakeHttpContext();
string pathToFile = context.Request.MapPath("~/static/all.js");
}
This may be helpful to someone. I had a related issue. Wanted to use an Excel file from a root-level folder within my c# Unit Test project.
I had a root-leve folder named "TestFiles". Inside I had "Test.xlsx".
What i did was:
Right-click on the "Test.xlsx", go to Properties and set "Copy To Output Directory" = "Copy Always"
Now the file and its containing folder "TestFiles" always get copied into the bin folder of the Unit Test project. So that I was able to use it like so:
var filePath = "TestFiles/Test.xlsx";
var strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filePath + ";Extended Properties=\"Excel 12.0;HDR=Yes;IMEX=0\"";
using (var conn = new OleDbConnection(strConn))
{
conn.Open();
...
}

Categories