简体   繁体   中英

Mocking a StreamWriter/determining when to mock

I have a class which uses a StreamWriter to write to a file.

public void CreateLog(string errorLogFilePath, StringBuilder errorLogBuilder, string errorMessage)
{
    using (StreamWriter sw = new StreamWriter(errorLogFilePath, true)
    {
        errorLogBuilder.Apend("An error was discovered.");
        //....
        sw.Write(errorLogBuilder.ToString());
    }
}

[Questions]

1: Is it possible to check that the .Write() method is called?

2: Do i need to wrap a MemoryStream inside the StreamWriter in order to test it, without actually accessing the hard drive. One of StreamWriters constructors accepts a stream but it states the following + will the UTF-8 encoding affect this?

Initializes a new instance of the StreamWriter class for the specified stream by using UTF-8 encoding and the default buffer size.

3: How do you determine if a class is actually accessing the hd and thus needs to be mocked? (sorry if this last question sounds stupid, but im genuinely a little puzzled by this.)

Have the method write to a TextWriter rather than a StreamWriter. Then test the method by passing it a mock TextWriter. In the "real" code, of course, you'll pass in a StreamWriter that was created using new StreamWriter(errorLogFilePath, true) .

This yields the following answers to your questions:

  1. Yes
  2. No
  3. You can't generally determine that without decompiling its code.

A little more detail:

Refactor the method into two methods:

public void CreateLog(string errorLogFilePath, StringBuilder errorLogBuilder, string errorMessage) 
{ 
    using (StreamWriter sw = new StreamWriter(errorLogFilePath, true) 
    {
        CreateLog(sw, errorLogBuilder, errorMessage); 
    } 
} 

public void CreateLog(TextWriter writer, StringBuilder errorLogBuilder, string errorMessage)
{
    errorLogBuilder.Apend("An error was discovered."); 
    //.... 
    writer.Write(errorLogBuilder.ToString()); 
}

Test the first method to ensure that it calls the second method with an appropriately-constructed StreamWriter. Test the second method to ensure that it calls Write on the passed TextWriter, with appropriate arguments. Now you've abstracted away the dependency on the hard drive. Your tests don't use the hard drive, but you're testing everything.

Generally speaking, you could :

Use a well tested logging library (like NLog, MS Logging Application Block), and spare you developping and maintaining your own.

Refactor your logging logic (or code calling messageboxes, open file dialogs, and so on) into a service, with its interface. This way you can split your testing strategy :

  • when testing consumers of the loggin service : mock the logging interface to make sure the log method is called. This will ensure that the logging is correctly called by consumers of your logging service
  • when testing the logging service implementation, just make sure that expected output matches given input : if you want to write "FOO" to bar.log, effectively call

IE :

// arrrange
File.Delete("bar.log")

// act
CreateLog("bar.log", errorLogBuilder, "FOO")

// assert
Assert.IsTrue( File.Exists("bar.log") )
Assert.IsTrue( File.ReadAllLines("bar.log").First() == "FOO")

The point is making sure that the component is called, done by mocking. Then you can check that the component works as expected.

I know this is a very old question, but I came across this while trying to solve a similar problem.. namely, how to fake the StreamWriter.

The way I went about this was by not having the StreamWriter created inside the method as part of the using statement, but created up front, within the ctor (make your class extend from IDisposable and then destroy the StreamWriter in the Dispose method instead). Then inject a fake over the top of it while under test:

internal class FakeStreamWriter : StreamWriter
{
    public List<string> Writes { get; set; } = new List<string>();

    public FakeStreamWriter() : base(new MemoryStream()) { }

    public override void Write(string value)
    {
        WriteLine(value);
    }
    public override void WriteLine(string value)
    {
        Writes.Add(value);
    }

    public override void Flush()
    {

    }
}

My unit test method then looks like this:

public void SmtpStream_Negotiate_EhloResultsCorrectly()
{
    var ctx = new APIContext();
    var logger = new FakeLogger();
    var writer = new FakeStreamWriter();
    var reader = new FakeStreamReader { Line = "EHLO test.com" };
    var stream = new SmtpStream(logger, ctx, new MemoryStream())
    {
        _writer = writer,
        _reader = reader
    };

    Exception ex = null;

    try
    {
        stream.Negotiate(ctx);
    }
    catch (Exception x)
    {
        ex = x;
    }

    Assert.IsNull(ex);
    Assert.IsTrue(writer.Writes.ElementAt(0) == "250 Hello test.com");
    Assert.IsTrue(writer.Writes.ElementAt(1) == "250 STARTTLS");
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM