How to write unitTest for methods using a stream as a parameter
Asked Answered
G

5

48

I have class ImportProvider , and I want write unit test for Import method.

But this should be unit test, so I don't want to read from file to stream. Any idea?

public class ImportProvider : IImportProvider
{ 
     public bool Import(Stream stream)
     {
         //Do import

         return isImported;
     }
}

public interface IImportProvider
{
      bool Import(Stream input);
}

This is unit test:

[TestMethod]
public void ImportProvider_Test()
{
    // Arrange           
    var importRepository = new Mock<IImportRepository>(); 
    var imp = new ImportProvider(importRepository.Object);
    //Do setup...

    // Act
    var test_Stream = ?????????????
    // This working but not option:
    //test_Stream = File.Open("C:/ExcelFile.xls", FileMode.Open, FileAccess.Read);
    var result = imp.Import(test_Stream);

    // Assert    
    Assert.IsTrue(result);
}
Geodesic answered 13/5, 2015 at 10:33 Comment(1)
Can you change production code?Telugu
P
84

Use a MemoryStream. Not sure what your function expects, but to stuff a UTF-8 string into it for example:

//Act
using (var test_Stream = new MemoryStream(Encoding.UTF8.GetBytes("whatever")))
{
    var result = imp.Import(test_Stream);

    // Assert    
    Assert.IsTrue(result);
}

EDIT: If you need an Excel file, and you are unable to read files from disk, could you add an Excel file as an embedded resource in your test project? See How to embed and access resources by using Visual C#

You can then read as a stream like this:

//Act
using (var test_Stream = this.GetType().Assembly.GetManifestResourceStream("excelFileResource"))
{
    var result = imp.Import(test_Stream);

    // Assert    
    Assert.IsTrue(result);
}
Pompei answered 13/5, 2015 at 10:37 Comment(10)
My function expect data from excel file. test_Stream = File.Open("C:/Excel file.xls", FileMode.Open, FileAccess.Read);Geodesic
Any reason you can't just load a test file like that?Pompei
I don't have permission to write integration test:)Geodesic
@user2451446, so your real problem is that you want to feed the contents of an Excel file into the code under test but do not want to read that data from an excel file. You're going to have to compromise somewhere: either make it an integration test or remove the dependency on the specific content of the stream.Sterne
Have added info on how to add the file as a resourcePompei
@GazTheDestroyer, I love it when I claim a dichotomy of poor choices and someone adds a third, perfect solution, choice to the mix. I never thought of resources. That completely solves the OP's needs. Sadly I can't +1 a second time :)Sterne
By the way, reading from a file turns the test into something different from what is broadly understood as an Unit Test.Telugu
I disagree. If the file is embedded into the test, then it is still test vs. one unit of code. You abstract away where the data actually comes from and test just that the data can be processed correctly. There are multiple ways to embed the data. The most obvious are to either generate an actual file and then make it an "Embedded Resource", or to write code against an Excel DOM provider of some sort that dynamically generates data in the expected format.Christogram
...and if you use code to generate an Excel file automatically at runtime (and then e.g. save it to a MemoryStream), you have the choice of either always generating exactly the same data, or of generating the data randomly -- in the latter case, you use an unpredictable seed for a pseudo-random number generator and output the seed as part of the test output, so that if it fails, the result can be replicated in subsequent debug runs to diagnose the issue.Christogram
Ultimately, as long as the data isn't coming from outside of the test, there is no real semantic difference between a string literal and an "Excel file literal" being used as input for a test case.Christogram
C
7

You can use a MemoryStream to provide a purely in-memory stream for your test.

Choreography answered 13/5, 2015 at 10:37 Comment(0)
P
5

If you're unit-testing code that accepts network streams, http streams, AWS S3 streams and other "non-seekable" streams, then using MemoryStream is not your best idea, because it's "seekable".

I.e. it's too nice and gentle, and allows all kinds of manipulations.

To battle-test code that works with these "rough" streams, close to real-life conditions I would suggest inheriting from a MemoryStream and then override .CanSeek (false) .Length (throw NotSupported) .Position setter (throw nonsupported) etc. etc.

Real life example: I was working with an image processing library, that accepted Stream as an input. My tests were working fine, because they were based on MemoryStreams and FileStreams. But once I deployed my code to production where I process images from Amazon S3, I got all kinds of exceptions because the image library was expecting a seekable stream.

Here's the code I use:

public class BadBoyStream : MemoryStream
{
    public BadBoyStream (byte[] buffer) : base(buffer) { }

    public override bool CanSeek => false;

    public override long Length => throw new NotSupportedException();

    public override long Position
    {
        get => base.Position;
        set => throw new NotSupportedException();
    }

    public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();

    public override void SetLength(long value) => throw new NotSupportedException();
}
Pretended answered 17/3, 2023 at 22:38 Comment(0)
T
0

My solution is to wrap Stream within your own class let's name it StreamWrapper which implements interface IStream. Now change Import signature to

public bool Import(IStream stream)

Now you are able to mock that stream and if you want to use it in production code you should pass StreamWrapper otherwise Mock.

Trenton answered 13/5, 2015 at 10:55 Comment(4)
How to mock stream with any meaningful data ?Geodesic
That won't help as apparently the method requires Excel data in the stream (see comments to GazTheDestroyer's answer).Sterne
@user2451446 it depends what is the content of Import method, what is used, what is expected etc DavidArno I don't understand what do you mean? If you wrap whole stream class you will be able to use all functionality that stream provides but with another class, I don't understand where would it fail.Trenton
@Zbigniew, that mock class would have to supply Excel data when requested. That data can't be read from a file ... so the OP has an impossible situation. The mock class doesn't solve anything that a MemoryStream can't solve too.Sterne
T
-2

Use an isolation framework like JustMock, TypeMock or Microsoft Fakes and you will be able to mock the Stream.

The bad news is that all of them as far as I know are paid.

Telugu answered 13/5, 2015 at 10:47 Comment(5)
I think it comes down to two things: 1) There are mocking frameworks like NSubstitute that are completely free to use, and 2) streams can be "mocked" without having to mock the actual stream type, because a) the Stream type itself can be directly extended in arbitrary subclasses, and b) if all you need is to be able to read or write data, the built-in MemoryStream type can supply an interface between Stream semantics and byte[] buffers. So, this answer does not accurately characterize the problem space or potential solutions.Christogram
@JonathanGilbert: you may think that there are other options and you may think that they are better than this one but I think that it is not arguable that an isolation framework IS a potential solution for this problem.Telugu
Okay, yes, it is technically true that you can use a mocking framework to make a mock Stream object. But, you can create a mock Stream object more easily with a simple subclass. On top of it, you state in your answer that all mocking frameworks are paid, which is just plain untrue. So, your answer is misleading, even if the first half of it is technically true, and your answer suggests doing something "the hard way", without indicating or acknowledging that there is an easier way. That is why other Stack Overflow members have rated it a poor answer.Christogram
This is incorrect, the Microsoft Fakes are not paid.The answer should be deleted.Kashgar
@SkorunkaFrantišek: not paid? It is only available on Visual Studio ultimate or premium. Do you know how much is each of these? It's the most expensive one :DTelugu

© 2022 - 2024 — McMap. All rights reserved.