Reputation: 3999
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);
}
Upvotes: 50
Views: 62122
Reputation: 967
I know that it's an old question, but I stumbled upon similar issue when trying to mock Stream
in my unit test. As many of you suggested here, we can use MemoryStream
in many cases, but it starts to be problematic if you want to check what was written to already closed Stream
.
Assuming I want to test following piece of code:
public static void ToJsonFile(object? value, string path)
{
// Open write stream for specified path
using var fileStream = FileSystem.WriteStream(path);
// Parse object to JSON string as memory stream
using var memoryStream = ToJsonStream(value, new MemoryStream());
// Write memory stream to a file
memoryStream.WriteTo(fileStream);
}
And I am able to mock WriteStream()
method, because FileSystem
is an interface:
public interface IFileSystem
{
public Stream WriteStream(string path);
}
I am not able to simply pass MemoryStream
as a mocked result of WriteStream()
function as it will get disposed before I'll be able to check what's inside:
[Test]
public void WriteStream_NotWorking_Test()
{
// Arrange - create file system mock
var fsMock = new Mock<IFileSystem>();
// Create new memory stream
var ms = new MemoryStream();
// Setup mock to return created memory stream
fsMock.Setup(fs => fs.WriteStream(It.IsAny<string>()))
.Returns(ms).Verifiable();
// Inject mocked file system
ParseSettings.FileSystem = fsMock.Object;
// Act - call method
Parse.ToJsonFile("hello", "any.json");
// ERROR! - System.ObjectDisposedException : Cannot access a closed Stream.
var data = Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
// Assert - check data
Assert.That(data, Does.Contain("hello"));
}
What I've come up with is to return mocked MemoryStream
that is using base calls, because otherwise the method under test would fail. During buffer writing it will grab what was written to the buffer and save it to variable. Then you can use saved data in assertion even though the MemoryStream
was already disposed:
[Test]
public void WriteStream_Working_Test()
{
// Arrange - create file system mock
var fsMock = new Mock<IFileSystem>();
// Create memory stream mock
var msMock = new Mock<MemoryStream>() { CallBase = true };
// Create variable for buffer content
byte[] bufferData = [];
// Setup memory stream mock to grab what was written to the buffer
msMock.Setup(ms => ms.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<byte[], int, int>((buffer, offset, count) =>
{
var bytes = offset > bufferData.Length
? bufferData.Concat(Enumerable.Repeat<byte>(0, offset - bufferData.Length))
: bufferData[0..offset];
bufferData = bytes.Concat(buffer.Take(count)).ToArray();
}).CallBase();
// Setup file system mock to return mocked memory stream
fsMock.Setup(fs => fs.WriteStream(It.IsAny<string>()))
.Returns(msMock.Object).Verifiable();
// Inject mocked file system
ParseSettings.FileSystem = fsMock.Object;
// Act - call method
Parse.ToJsonFile("hello", "any.json");
// Get data from buffer - this will work now as it is using saved buffer instead of closed stream
var data = Encoding.UTF8.GetString(bufferData, 0, bufferData.Length);
// Assert - check data
Assert.That(data, Does.Contain("hello"));
}
I hope it'll help someone in the future as I spend quite some time to figure that out, trying some hardcore stuff like UnmanagedMemoryStream
:)
Upvotes: 0
Reputation: 60822
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();
}
Upvotes: 7
Reputation: 21261
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);
}
Upvotes: 84
Reputation: 6232
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.
Upvotes: 0
Reputation: 21855
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.
Upvotes: -2
Reputation: 2522
You can use a MemoryStream to provide a purely in-memory stream for your test.
Upvotes: 7