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();
...
}
Related
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 MSTest in a .Net Core Unit test project. I am attempting to use a csv datasource to provide the data for a test method.
Previously, I would use something like below in a .Net Framework test project:
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", #"data.csv", "data#csv", DataAccessMethod.Sequential),
DeploymentItem("data.csv"),
TestMethod]
public void ValuesController_Post()
{
_controller.Post(TestContext.DataRow["body"]);
_valuesRepository.Verify(_ => _.Post(It.IsAny<string>()), Times.Once);
}
The key here being the DataRow property found in TestContext. This doesn't appear to exist in the .Net Core version of the TestContext.
How would I go about doing this in .Net Core?
Since moving to aspnet core, I've never been able to use the same [Datasource(...)] attribute to iterate through test data, my data-driven tests are always skipped.
Have you considered switching to another approach with [DataTestMethod] and [DynamicData] with a custom source that reads you file ?
Here's a good article on this :
https://www.meziantou.net/2018/02/05/mstest-v2-data-tests
Maybe another way would be to read the whole file at the begining of the test and then iterate through the dataset as One single unit test?
Hope this helps.
It took me an afternoon to fiddle with things, but I finally found a solution. Since you don't specify your test or CSV file, here is a quick example I could get working.
Long story short, I installed the CsvHelper NuGet package, because parsing CSV is dead easy right up to the point it is not. As Carl Verret pointed out, you need to use the [DynamicData(...)] attribute above your test method, and then parse the CSV using CsvHelper.
The CSV File (Example.csv)
A,B,IsLessThanZero
1,2,FALSE
3,-5,TRUE
Important: Make sure this CSV file is included in your test project and "Copy To Output Directory" is set to "Always" in the properties for the CSV file in Solution Explorer.
Data Transfer Object Used By CsvHelper
public class AdditionData
{
public int A { get; set; }
public int B { get; set; }
public bool IsLessThanZero { get; set; }
}
The Test Class
[TestClass]
public class ExampleTests
{
// HINT: Look in {Your Test Project Folder}\bin\{Configuration}\netcore3.1\FolderYourCsvFileIsIn for the CSV file.
// Change this path to work with your test project folder structure.
private static readonly string DataFilePath = Path.GetDirectoryName(typeof(ExampleTests).Assembly.Location) + #"\FolderYourCsvFileIsIn\Example.csv";
[TestMethod]
[DynamicData(nameof(GetData), DynamicDataSourceType.Method)]
public void AddingTwoNumbers(AdditionData data)
{
bool isLessThanZero = data.A + data.B < 0;
Assert.AreEqual(data.IsLessThanZero, isLessThanZero);
}
private static IEnumerable<object[]> GetData()
{
using var stream = new StreamReader(DataFilePath);
using var reader = new CsvReader(stream, new CsvConfiguration(CultureInfo.CurrentCulture));
var rows = reader.GetRecords<AdditionData>();
foreach (var row in rows)
{
yield return new object[] { row };
}
}
}
After building your solution, you will see a single test in Test Explorer. Running this single test runs all variants defined in your CSV 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();
}
...
}
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"));
I have the folowing tests:
[TestClass]
public class GeneralTest
{
[TestMethod]
public void VerifyAppDomainHasConfigurationSettings()
{
string value = ConfigurationManager.AppSettings["TestValue"];
Assert.IsFalse(String.IsNullOrEmpty(value), "No App.Config found.");
}
[TestMethod]
[HostType("Moles")]
public void VerifyAppDomainHasConfigurationSettingsMoles()
{
string value = ConfigurationManager.AppSettings["TestValue"];
Assert.IsFalse(String.IsNullOrEmpty(value), "No App.Config found.");
}
}
The only difference between them is [HostType("Moles")]. But the first passes and the second fails. How can I read App.config from the second test?
Or may be I can add some another config file in other place?
Assuming you are trying to access values in appSettings, how about just adding the configuration at the beginning of your test. Something like:
ConfigurationManager.AppSettings["Key"] = "Value";
Then when your test tries to read the AppSettings "Key", "Value" will be returned.
You just add your "App.Config" file to the unit test project . It will read automatically.
See http://social.msdn.microsoft.com/Forums/en/pex/thread/9b4b9ec5-582c-41e8-8b9c-1bb9457ba3f6
In the mean time, as a work around, you could try adding the configuration settings to Microsoft.Moles.VsHost.x86.exe.config
[ClassInitialize]
public static void MyClassInitialize(TestContext testContext)
{
System.Configuration.Moles.MConfigurationManager.GetSectionString =
(string configurationName) =>
{
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
Assembly assembly = Assembly.GetExecutingAssembly();
fileMap.ExeConfigFilename = assembly.Location + ".config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
object section = config.GetSection(configurationName);
if (section is DefaultSection)
{
ConfigurationSection configurationSection = (ConfigurationSection) section;
Type sectionType = Type.GetType(configurationSection.SectionInformation.Type);
if (sectionType != null)
{
IConfigurationSectionHandler sectionHandler =
(IConfigurationSectionHandler)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(sectionType.Assembly.FullName, sectionType.FullName);
section =
sectionHandler.Create(
configurationSection.SectionInformation.GetParentSection(),
null,
XElement.Parse(configurationSection.SectionInformation.GetRawXml()).ToXmlNode());
}
}
return section;
};
}
I ran across this issue at work and didn't like any of these answers. I also have the problem that the configuration file is being read in a static constructor which means I can't Mole ConfigurationManager before the static constructor is executed.
I tried this on my home computer and found that the configuration file was being read correctly. It turns out I was using Pex 0.94.51006.1 at home. This is slightly older than the current one. I was able to find a download for the older academic version:
http://research.microsoft.com/en-us/downloads/d2279651-851f-4d7a-bf05-16fd7eb26559/default.aspx
I installed this on my work computer and everything is working perfectly. At this point, I'm downgrading to the older version until a newer working version is released.
This is what I am using to get the correct AppConfig and ConnectionString sections:
var config = System.Configuration.ConfigurationManager.OpenExeConfiguration(Reflection.Assembly.GetExecutingAssembly().Location);
typeof(Configuration.ConfigurationElementCollection).GetField("bReadOnly", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(System.Configuration.ConfigurationManager.ConnectionStrings, false);
foreach (Configuration.ConnectionStringSettings conn in config.ConnectionStrings.ConnectionStrings)
System.Configuration.ConfigurationManager.ConnectionStrings.Add(conn);
foreach (Configuration.KeyValueConfigurationElement conf in config.AppSettings.Settings)
System.Configuration.ConfigurationManager.AppSettings(conf.Key) = conf.Value;
Saw the ConnectionString part here