So I tried to create a very simple XmlFileWriter
public class XmlFileWriter
{
public void WriteTo<TSerializationData>(string path, TSerializationData data)
{
using (StreamWriter streamWriter = new StreamWriter(path))
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(TSerializationData));
xmlSerializer.Serialize(streamWriter, data);
}
}
}
and XmlFileReader
public class XmlFileReader
{
public TSerializationData ReadFrom<TSerializationData>(string path)
{
using (StreamReader streamReader = new StreamReader(path))
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(TSerializationData));
return (TSerializationData) xmlSerializer.Deserialize(streamReader);
}
}
}
I want to create unit tests for both of them with xUnit. Since they are coupled to the filesystem I was looking for a way to mock it somehow. Many Posts highly recommend the System.IO.Abstractions package and the additional TestingHelpers.
I will only show the tests for the reader for now since both scenarios are very similiar. This is what I have so far
[Fact]
public void ThrowsExceptionIfPathIsInvalid()
{
XmlFileReader xmlFileReader = new XmlFileReader();
// use an empty path since it should be invalid
Assert.Throws<Exception>(() => xmlFileReader.ReadFrom<object>(string.Empty));
}
[Fact]
public void DeserializesDataFromXmlFile()
{
// Generate dummy data with default values
MyDummyClass dummyData = new MyDummyClass();
XmlFileWriter xmlFileWriter = new XmlFileWriter();
XmlFileReader xmlFileReader = new XmlFileReader();
string filePath = "???"; // TODO
// Generate a new file and use it as a mock file
xmlFileWriter.WriteTo(filePath, dummyData);
// Read from that file
MyDummyClass fileContent = xmlFileReader.ReadFrom<MyDummyClass>(filePath);
// Compare the result
Assert.Equal(dummyData, fileContent);
}
I'm struggling with decoupling the real Filesystem. How would I make the XmlSerializer class use a fake filesystem? I installed the abstractions package but I don't know how to use it for this case (for reading and writing).
StreamReader and StreamWriter both have constructors that accept a Stream. I recommend making your method also take streams as parameters, and the your unit tests can supply a MemoryStream containing your test xml as a string (which can be hardcoded), while your actual application can provide a FileStream that is the file on disk. Like so:
public void WriteTo<TSerializationData>(Stream location, TSerializationData data)
{
// Code here doesn't change
}
public TSerializationData ReadFrom<TSerializationData>(Stream location)
{
// Code here doesn't change
}
Then in your tests you can do:
using (var ms = new MemoryStream())
{
using (var sr = new StreamWriter())
{
sr.Write("<xml>This is your dummy XML string, can be anything you want</xml>");
}
MyDummyClass fileContent = xmlFileReader.ReadFrom<MyDummyClass>(ms);
}
And if you want to read from a file you can do:
// Using whatever FileMode/ FileAccess you need
MyDummyClass fileContent;
using (var fs = File.Open(#"C:\Path\To\File.xml", FileMode.Open, FileAccess.Read))
{
fileContent = xmlFileReader.ReadFrom<MyDummyClass>(fs);
}
Related
In C# Dot Net, How to handle a exception when you want to de-serialize a xml file, but by default the file doesn't exists! because you have to run the program to create one.
Below is the area where I need Help.
public static Compare_Data[] Deserialize()
{
Compare_Data[] cm;
cm = null;
string path = #"C:\Users\XYZ\Desktop\BACKUP_DATA\log.xml";
XmlSerializer xs = new XmlSerializer(typeof(Compare_Data[]));
if (File.Exists(path))
{
using (FileStream fs = new FileStream(path, FileMode.Open))
{
// This will read the XML from the file and create the new instance of Compare_Data.
cm = (Compare_Data[])xs.Deserialize(fs);
return cm;
}
}
else
{
using (FileStream fs = new FileStream(path, FileMode.Create))
{
xs.Serialize(fs); /// what to add here ?
}
}
return null;
}
If general, you don't want your methods to have side effects. In this case, creating an empty log file in the else branch is probably unnecessary and should be handled by a separate Serialize() method when there is data to be logged.
Your code could be simplified something like this:
public static Compare_Data[] Deserialize()
{
const string path = #"C:\Users\XYZ\Desktop\BACKUP_DATA\log.xml";
if (!File.Exists(path))
{
// return null or an empty array, depending on how
// you want the calling code to handle this.
return null;
}
using (FileStream fs = new FileStream(path, FileMode.Open))
{
var xs = new XmlSerializer(typeof(Compare_Data[]));
return (Compare_Data[])xs.Deserialize(fs);
}
}
This question already has answers here:
How to save/restore serializable object to/from file?
(6 answers)
Closed 5 years ago.
I'm creating an application in C# Universal Windows and I would like to know how would would I go about writing data to a file so that I can read from it later. I was thinking of making a class System.Serializable and then write objects of that class as files to the user's device so then I can read those files back as objects again but I don't know how to go about doing that.
Use the File class. It has all the abilities you need - open a file, read its contents, edit, save or delete.
From MSDN:
public static void Main()
{
string path = #"c:\temp\MyTest.txt";
if (!File.Exists(path))
{
// Create a file to write to.
using (StreamWriter sw = File.CreateText(path))
{
sw.WriteLine("Hello");
sw.WriteLine("And");
sw.WriteLine("Welcome");
}
}
// Open the file to read from.
using (StreamReader sr = File.OpenText(path))
{
string s = "";
while ((s = sr.ReadLine()) != null)
{
Console.WriteLine(s);
}
}
}
In the .NET framework, there is a namespace called System.IO.
System.IO.StreamWriter writer = new System.IO.StreamWriter(filepath);
You can use the StreamWriter from the System.IO namespace to write to a file. The constructor simply takes in a string variable to the path of the file you want to write to. Then you can use a StreamReader (like so):
System.IO.StreamReader reader = new System.IO.StreamReader(filepath);
to read the file back again.
Here is an example:
class Program
{
[Serializable]
public class MyClass
{
public string Property1{ get; set; }
public string Property2 { get; set; }
}
static void Main(string[] args)
{
var item = new MyClass();
item.Property1 = "value1";
item.Property2 = "value2";
// write to file
FileStream s = new FileStream("myfile.bin", FileMode.Create);
BinaryFormatter f = new BinaryFormatter();
f.Serialize(s,item);
s.Close();
// read from file
FileStream s2 = new FileStream("myfile.bin", FileMode.OpenOrCreate,FileAccess.Read);
MyClass item2 = (MyClass)f.Deserialize(s2);
}
}
I'm working within my PCL library and need to serialise a class and output to a file. I'm very short on space, so don't have the space for PCLStorage.
Currently I'm using this for the serialisation. IFilePath returns a file path from the non-PCL part.
IFilePath FilePath;
public void SerializeObject<T>(T serializableObject, string fileName)
{
if (serializableObject == null) { return; }
try
{
using (var ms = new MemoryStream())
{
var xmlDocument = new XDocument();
using (var writer = xmlDocument.CreateWriter())
{
var serialize = new DataContractSerializer(typeof(T));
serialize.WriteObject(writer, serializableObject);
xmlDocument.Save(ms, SaveOptions.None);
}
}
}
catch (Exception ex)
{
//Log exception here
}
}
When I try to save, nothing is showing. I have a feeling it's because I'm not outputting the stream to a file, but I'm at a loss as how to do this.
You are trying to save to a file, an action which is specific for each platform.
PCLStorage is implementing this functionality for each platform and this is what you will have to do also if you can"t use it.
In you case what you have to do is to create the stream (in each platform) in your non pcl code and then pass it to your function which will look like this:
public void SerializeObject<T>(T serializableObject, Stream fileStream)
{
if (serializableObject == null) { return; }
try
{
var xmlDocument = new XDocument();
using (var writer = xmlDocument.CreateWriter())
{
var serialize = new DataContractSerializer(typeof(T));
serialize.WriteObject(writer, serializableObject);
xmlDocument.Save(fileStream, SaveOptions.None);
}
}
catch (Exception ex)
{
//Log exception here
}
}
more on pcl here.
Problem is that your variable ms in using (var ms = new MemoryStream()) is empty and does not point to any file location of which MemoryStream does not receive a filepath as argument. I propose you use a StreamWriter instead and pass the your FileStream to it. Example
Use your fileName to create a FileStream which inherits from the Stream class then replace the Memory stream with the newly created filestream like this.
using(FileStream stream = File.OpenWrite(fileName))
{
var xmlDocument = new XDocument();
using (var writer = xmlDocument.CreateWriter())
{
var serialize = new DataContractSerializer(typeof(T));
serialize.WriteObject(writer, serializableObject);
xmlDocument.Save(stream, SaveOptions.None);
}
}
Hope this helps.
I currently have a method like this
public void BusinessMethod(object value, StreamWriter sw)
{
//Calls a private method that converts the data in `value` to a custom formatted string variable `str`
string str = myPrivateMethod(value);
//write the string to stream
sw.Write(str);
}
I am trying to test this method using the approach mentioned here and have done exactly the same thing. However, my result string comes back as an empty string. I cannot change the method signature. How does one test a method like this? I am using Nunit for testing.
This is my test method
[Test]
public void My_Test()
{
MyPoco dto = new MyPoco ();
//set up the dto properties here
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))
{
sut.BusinessMethod(dto, writer);
string result = Encoding.UTF8.GetString(stream.ToArray());
}
}
You need to Close/Flush/Dispose writer so it actually commits changes to stream:
using (var stream = new MemoryStream())
{
using (var writer = new StreamWriter(stream))
{
sut.BusinessMethod(dto, writer);
}
// moved outside of inner using to ensure writer stored content to stream
string result = Encoding.UTF8.GetString(stream.ToArray());
}
I have the following code I'm trying to write a unit test for:
// Grab all of the content.
var rawContent = new StringBuilder();
foreach (var fileInfo in response.Files)
{
using (var stream = fileInfo.VirtualFile.Open())
{
using (var streamReader = new StreamReader(stream))
{
rawContent.Append(streamReader.ReadToEnd());
}
}
}
Now, I have mocked the Stream instance that Open() returns. That's easy. The stream object is also a MemoryStream, btw.
But I'm not sure how to setup the ReadToEnd() .. to return some string content I have. This is because the StreamReader instance is private to this method.
I thought of providing a Property which can be set.
eg.
public string AStreamReader { get; set; }
then doing this ..
// Grab all of the content.
var rawContent = new StringBuilder();
foreach (var fileInfo in response.Files)
{
using (var stream = fileInfo.VirtualFile.Open())
{
using (var streamReader = AStreamReader ?? new StreamReader(stream))
{
rawContent.Append(streamReader.ReadToEnd());
}
}
}
and then just passing a mock StreamReader through the AStreamReader property.
But I don't really want to do this. I feel that I'm leaking some internal logic, unnecessarily, and only for mocking! A real user will never set that, so that feels like a really dirty thing to do.
Perhaps I'm missing something but if you are able to mock the input Stream, why do you need to mock the StreamReader?
Does the following not work for you?
private string ReadFromResponseFiles(Stream[] inputs)
{
var rawContent = new StringBuilder();
foreach (var stream in inputs)
{
using (stream)
{
using (var streamReader = new StreamReader(stream))
{
rawContent.Append(streamReader.ReadToEnd());
}
}
}
return rawContent.ToString();
}
[Test]
public void Can_read_files()
{
var file1 = new MemoryStream(Encoding.UTF8.GetBytes("Foo"));
var file2 = new MemoryStream(Encoding.UTF8.GetBytes("Bar"));
var result = ReadFromResponseFiles(new[] { file1, file2 });
Assert.AreEqual("FooBar", result);
file1.Dispose();
file2.Dispose();
}
}
Set the modifier as protected, and subclass it. It's how we do with most of our tests, in such cases as this.