unit testing a void method with StreamWriter as one of the parameters - c#

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());
}

Related

How to test XML De-/Serialization

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);
}

JToken.WriteToAsync does not write to JsonWriter

I'm trying to create a middleware that changes the request in a certain way. I am able to read it and change the content but I cannot figure out how to correctly setup the stream writers to create a new body. When I call normalized.WriteToAsync(jsonWriter) the MemoryStream remains empty and consequently I receive the A non-empty request body is required. exception. What am I missing here? This is what I have so far:
public async Task Invoke(HttpContext context)
{
if (context.Request.ContentType == "application/json" && context.Request.ContentLength > 0)
{
using var scope = _logger.BeginScope("NormalizeJson");
try
{
using var requestReader = new HttpRequestStreamReader(context.Request.Body, Encoding.UTF8);
using var jsonReader = new JsonTextReader(requestReader);
var json = await JToken.LoadAsync(jsonReader);
var normalized = _normalize.Visit(json); // <-- Modify json and return JToken
// Create new Body
var memoryStream = new MemoryStream();
var requestWriter = new StreamWriter(memoryStream);
var jsonWriter = new JsonTextWriter(requestWriter);
await normalized.WriteToAsync(jsonWriter); // <-- At this point the MemoryStream has still 0 length.
var content = new StreamContent(memoryStream.Rewind()); // <-- Use helper extension to Seek.Begin = 0
context.Request.Body = await content.ReadAsStreamAsync();
}
catch (Exception e)
{
_logger.Scope().Exceptions.Push(e);
}
}
await _next(context);
}
Demo for LINQPad etc.:
async Task Main()
{
var token = JToken.FromObject(new User { Name = "Bob" });
var memoryStream = new MemoryStream();
var requestWriter = new StreamWriter(memoryStream);
var jsonWriter = new JsonTextWriter(requestWriter);
await token.WriteToAsync(jsonWriter);
memoryStream.Length.Dump(); // <-- MemoryStream.Length = 0
}
public class User
{
public string Name { get; set; }
}
You need to properly flush and close your JsonTextWriter and StreamWriter in order to fully populate the memoryStream, like so:
var memoryStream = new MemoryStream();
// StreamWriter implements IAsyncDisposable
// Leave the underlying stream open
await using (var requestWriter = new StreamWriter(memoryStream, leaveOpen: true))
{
var jsonWriter = new JsonTextWriter(requestWriter); // But JsonTextWriter does not implement IAsyncDisposable, only IDisposable!
try
{
await token.WriteToAsync(jsonWriter);
}
finally
{
await jsonWriter.CloseAsync();
}
}
Demo fiddle #1 here.
Or, since you're writing to a MemoryStream, there's really no nead to use async at all, and instead you can do:
var memoryStream = new MemoryStream();
using (var requestWriter = new StreamWriter(memoryStream, leaveOpen: true)) // Leave the underlying stream open
using (var jsonWriter = new JsonTextWriter(requestWriter))
{
token.WriteTo(jsonWriter);
}
Demo fiddle #2 here.
Notes:
Note the use of await using for the StreamWriter. This syntax guarantees that the StreamWriter will be flushed and closed asynchronously, and can be used on any object that implements IAsyncDisposable. (This only really matters if you were writing to a file stream or other non-memory stream.)
It seems that neither JsonTextWriter nor the base class JsonWriter implement IAsyncDisposable, so I had to asynchronously close the JSON writer manually rather than via a using statement. The outer await using should ensure that the underlying StreamWriter is not left open in the event of an exception.
JSON RFC 8259 specifies that Implementations MUST NOT add a byte order mark (U+FEFF) to the beginning of a networked-transmitted JSON text. Thus, when constructing a StreamWriter, it is recommended to pass an encoding such as new UTF8Encoding(false) that does not prepend a BOM. Alternatively, if you just want UTF-8, the StreamWriter constructors will create a StreamWriter with UTF-8 encoding without a Byte-Order Mark (BOM) if you do not specify one yourself and leave a default value for that parameter as is shown in the code above.

Passing Stream to a Funtion and Leaving it Open

I am having trouble writing to stream. I think i understand why, but im not really following how to get around the issue. Please see example code below
The requirement that the caller of Writer in the example below manages the stream, not the Writer.
this is example code in the caller:
using (Stream stream1 = new MemoryStream())
using (Stream stream2 = new FileStream("ex.txt", FileMode.Create))
{
//example 1:
Writer writer1 = new Writer();
writer1.WriteToStream(stream1);
//example 2:
Writer writer2 = new Writer();
writer2.WriteToStream(stream2);
}
This is the Writer class: its supposed to leave stream open after its done with it.
public class Writer
{
public void WriteToStream(Stream destination)
{
Write(destination, "abc");
}
private void Write(Stream destination, string data)
{
Streamwriter sw = new StreamWriter(destination);
sw.Write(data);
}
}
in this setup, nothing shows up in MemoryStream or "ex.txt". I am guessing its because once you exit the Write method, StreamWriter is out of context, and stream goes with it before it gets a chance to be written to a file.
If I change the Write method to the example below, then i can get something to show up in the file, but the stream ends up closed, which goes against the requirement:
private void Write(Stream destination, string data)
{
using(Streamwriter sw = new StreamWriter(destination))
{
sw.Write(data);
}
}
So, how do I write a string to a stream (Memory of File), without closing the stream in the process. thank you
You are missing Stream Flush.
Following will solve the issue:
private void Write(Stream destination, string data)
{
var sw = new StreamWriter(destination);
sw.Write(data);
sw.Flush();
}
However, open streams are notorious.
The easiest way is probably to use the StreamWriter constructor that allows us to leave the underlying Stream open and set leaveOpento true.
private void Write(Stream destination, string data)
{
using (var sw = new StreamWriter(destination, Encoding.UTF8, 1024, leaveOpen: true))
{
sw.Write(data);
}
}

How can I mock .NET code that refers to a private object instance?

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.

Writing to an XML Document in Windows Phone 7

I'm attempting to write some data from an object to an XML document and am following a tutorial online, however I have run into a problem which I can't seem to fathom, the code I'm using to initiate the creation of the document isusing (XmlWriter writer = XmlWriter.Create("myData.xml")) and I'm getting an error with the "myData.xml", the errors I get are:
The best overload method match for 'System.Xml.XmlWriter.Create(System.Xml.XmlWriter)'
has some invalid arguments
Argument 1: cannot convert from 'string' to 'System.Xml.XmlWriter'
Is XmlWriter compatible with Windows Phone? And if not will I have to change huge amounts of code that writes to the file?
Edit: Here's my code
string output = SerializeToString<AppData>(rulesData);
using (XmlWriter writer = XmlWriter.Create(output))
{
writer.WriteStartDocument();
writer.WriteStartElement("myData");
writer.WriteElementString("Starting Cash", rulesData.myStartingCash);
writer.WriteElementString("Land on Go Data", rulesData.myLandOnGo);
writer.WriteElementString("Free Parking Data", rulesData.myFreeParking);
writer.WriteElementString("Full Circuit Data", rulesData.myFullCircuit);
writer.WriteElementString("Auction Data", rulesData.myAuction);
writer.Flush();
writer.WriteEndElement();
writer.WriteEndDocument();
}
Thanks! -Ryan
You can use this code:
public static void SerializeToStream<T>(Stream stream, object model)
{
var writer = XmlWriter.Create(stream);
var s = new XmlSerializer(typeof(T));
s.Serialize(writer, model);
}
public static string SerializeToString<T>(object model)
{
var xmlSer = new XmlSerializer(typeof(T));
using (var stream = new MemoryStream())
{
SerializeToStream<T>(stream, model);
var s = stream.ToArray();
return System.Text.Encoding.UTF8.GetString(s, 0, s.Length);
}
}
public static void SerializeToFile<T>(string filename, object model)
{
using (FileStream stream = File.Open(filename, FileMode.Create))
{
SerializeToStream<T>(stream, model);
}
}
Your code doesn't compile because you pass a string instead of a stream to XmlWriter
Usage:
string output = SerializeToString<ClassName>(instanceOfClass);

Categories