为密封的 PngBitmapEncoder 伪造流
Faking Stream for sealed PngBitmapEncoder
这是 的后续问题。
如何让 PngBitmapEncoder 接受 wrapped/mocked FileStream?
在 BitmapService.SaveBitmapAsPngImage() 中我想断言位图编码器调用流保存:
bitmapEncoder.Save(outStream.StreamInstance);
Rhino 模拟测试需要 "a valid" PngBitmapEncoder 的 FileStream:
[Test]
public void BitmapService_Should_SaveBitmapAsPngImage_RhinoMocks()
{
// Arrange
IFile fileMock = MockRepository.GenerateStrictMock<IFile>();
IFileStream fileStreamWrapperMock = MockRepository.GenerateStub<IFileStream>();
fileMock.Expect(x => x.Open(string.Empty, FileMode.OpenOrCreate))
.IgnoreArguments().Return(fileStreamWrapperMock);
var bitmapEncoderFactory = MockRepository.GenerateStub<IBitmapEncoderFactory>();
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
bitmapEncoderFactory.Expect(x => x.CreateBitmapEncoder()).Return(pngBitmapEncoder);
BitmapService sut = new BitmapService(fileMock, new PngBitmapEncoderFactory());
Size renderSize = new Size(100, 50);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
// Act
sut.SaveBitmapAsPngImage(new Uri("//A_valid_path"), renderBitmap);
// Assert
pngBitmapEncoder.AssertWasCalled(x => x.Save(fileStreamWrapperMock.FileStreamInstance));
}
Rhino 测试结果:
System.ArgumentNullException : Value cannot be null.
Parameter name: stream
at System.Windows.Media.StreamAsIStream.IStreamFrom(Stream stream)
at System.Windows.Media.Imaging.BitmapEncoder.Save(Stream stream)
at BitmapService.SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap)
我更喜欢 Rhino 模拟,但这里是 的最小起订量测试。不幸的是,它也不起作用:
[TestMethod]
public void BitmapService_Should_SaveBitmapAsPngImage()
{
//Arrange
var mockedStream = Mock.Of<Stream>(_ => _.CanRead == true && _.CanWrite == true);
Mock.Get(mockedStream).SetupAllProperties();
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock
.Setup(_ => _.OpenOrCreateFileStream(It.IsAny<string>()))
.Returns(mockedStream);
var sut = new BitmapService(fileSystemMock.Object);
Size renderSize = new Size(100, 50);
var renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
var path = new Uri("//A_valid_path");
//Act
sut.SaveBitmapAsPngImage(path, renderBitmap);
//Assert
Mock.Get(mockedStream)
.Verify(_ => _.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()));
}
起订量测试结果:
BitmapServiceTest.BitmapService_Should_SaveBitmapAsPngImage threw
exception: System.IO.IOException: Cannot read from the stream. --->
System.Runtime.InteropServices.COMException: Exception from HRESULT:
0x88982F72
at System.Windows.Media.Imaging.BitmapEncoder.Save(Stream stream)
因此测试尝试实际使用该 Stream。
Class 测试中:
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using SystemInterface.IO;
using SystemWrapper.IO;
public interface IBitmapService
{
void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap);
}
public interface IBitmapEncoderFactory
{
BitmapEncoder CreateBitmapEncoder();
}
public class PngBitmapEncoderFactory : IBitmapEncoderFactory
{
public BitmapEncoder CreateBitmapEncoder()
{
return new PngBitmapEncoder();
}
}
public class BitmapService : IBitmapService
{
private readonly IFile _fileWrapper;
private readonly IBitmapEncoderFactory _bitmapEncoderFactory;
public BitmapService(IFile fileWrapper, IBitmapEncoderFactory bitmapEncoderFactory)
{
_fileWrapper = fileWrapper;
_bitmapEncoderFactory = bitmapEncoderFactory;
}
public void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap)
{
// Create a file stream for saving image
using (IStream outStream = _fileWrapper
.Open(path.LocalPath, FileMode.OpenOrCreate))
{
// Use bitmap encoder for our data
BitmapEncoder bitmapEncoder = _bitmapEncoderFactory.CreateBitmapEncoder();
// push the rendered bitmap to it
bitmapEncoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// The problem: Expects real Stream as parameter!!!
bitmapEncoder.Save(outStream.StreamInstance);
}
}
}
正如我在评论中建议的那样,如果使用模拟框架模拟 Stream
不适合您,请考虑通过包装 MemoryStream
、
public class MockedFileStream : MemoryStream {
protected override void Dispose(bool disposing) {
//base.Dispose(disposing);
//No Op fr the purposes of the test.
}
public override void Close() {
//base.Close();
//No Op fr the purposes of the test.
}
public void CustomDispose() {
base.Dispose(true);
GC.SuppressFinalize(this);
}
}
这将为您准备好所有管道。唯一的问题是您必须覆盖 Dispose
方法,因为流正在与 using
语句一起使用。
测试将更新为使用 "fake" 流
[TestMethod]
public void BitmapService_Should_SaveBitmapAsPngImage() {
//Arrange
var mockedStream = new MockedFileStream();
var fileSystemMock = new Mock<ImageDrawingCombiner3.IFileSystem>();
fileSystemMock
.Setup(_ => _.OpenOrCreateFileStream(It.IsAny<string>()))
.Returns(mockedStream);
var sut = new ImageDrawingCombiner3.BitmapService(fileSystemMock.Object);
Size renderSize = new Size(100, 50);
var renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
var path = new Uri("//A_valid_path");
//Act
sut.SaveBitmapAsPngImage(path, renderBitmap);
//Assert
mockedStream.Length.Should().BeGreaterThan(0); //data was written to it.
mockedStream.CustomDispose(); //done with stream
}
在我看来,真的没有必要模拟或抽象 PngBitmapEncoder
,因为这是一个实现问题。
非常感谢@NKosi 引领了正确的方向。
为了完整起见,我想 post NUnit、Rhino 模拟测试。
[Test]
public void ShouldSaveBitmapAsPngImage()
{
// Arrange
Uri pathToFile = new Uri("//A_valid_path");
IFileSystem fileSystemMock = MockRepository.GenerateStrictMock<IFileSystem>();
MockedFileStream mockedFileStream = new MockedFileStream();
fileSystemMock.Expect(x => x.OpenOrCreateFileStream(pathToFile.AbsolutePath))
.IgnoreArguments().Return(mockedFileStream);
BitmapService sut = new BitmapService(fileSystemMock);
Size renderSize = new Size(100, 50);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
// Act
sut.SaveBitmapAsPngImage(pathToFile, renderBitmap);
// Assert
// Was data was written to it?
Assert.That(mockedFileStream.Length, Is.GreaterThan(0));
mockedFileStream.CustomDispose(); //done with stream
}
并从服务中删除了 BitmapEncoder 抽象:
public class BitmapService : IBitmapService
{
private readonly IFileSystem _fileSystem;
public BitmapService(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
public void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap)
{
// Create a file stream for saving image
using (Stream outStream = _fileSystem.OpenOrCreateFileStream(path.LocalPath))
{
// Use bitmap encoder for our data
BitmapEncoder bitmapEncoder = new PngBitmapEncoder();
// push the rendered bitmap to it
bitmapEncoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
bitmapEncoder.Save(outStream);
}
}
}
我对整洁干净的解决方案非常满意。感谢@NKosi 和 Whosebug ;)
这是
如何让 PngBitmapEncoder 接受 wrapped/mocked FileStream?
在 BitmapService.SaveBitmapAsPngImage() 中我想断言位图编码器调用流保存: bitmapEncoder.Save(outStream.StreamInstance);
Rhino 模拟测试需要 "a valid" PngBitmapEncoder 的 FileStream:
[Test]
public void BitmapService_Should_SaveBitmapAsPngImage_RhinoMocks()
{
// Arrange
IFile fileMock = MockRepository.GenerateStrictMock<IFile>();
IFileStream fileStreamWrapperMock = MockRepository.GenerateStub<IFileStream>();
fileMock.Expect(x => x.Open(string.Empty, FileMode.OpenOrCreate))
.IgnoreArguments().Return(fileStreamWrapperMock);
var bitmapEncoderFactory = MockRepository.GenerateStub<IBitmapEncoderFactory>();
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
bitmapEncoderFactory.Expect(x => x.CreateBitmapEncoder()).Return(pngBitmapEncoder);
BitmapService sut = new BitmapService(fileMock, new PngBitmapEncoderFactory());
Size renderSize = new Size(100, 50);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
// Act
sut.SaveBitmapAsPngImage(new Uri("//A_valid_path"), renderBitmap);
// Assert
pngBitmapEncoder.AssertWasCalled(x => x.Save(fileStreamWrapperMock.FileStreamInstance));
}
Rhino 测试结果:
System.ArgumentNullException : Value cannot be null. Parameter name: stream
at System.Windows.Media.StreamAsIStream.IStreamFrom(Stream stream)
at System.Windows.Media.Imaging.BitmapEncoder.Save(Stream stream)
at BitmapService.SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap)
我更喜欢 Rhino 模拟,但这里是
[TestMethod]
public void BitmapService_Should_SaveBitmapAsPngImage()
{
//Arrange
var mockedStream = Mock.Of<Stream>(_ => _.CanRead == true && _.CanWrite == true);
Mock.Get(mockedStream).SetupAllProperties();
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock
.Setup(_ => _.OpenOrCreateFileStream(It.IsAny<string>()))
.Returns(mockedStream);
var sut = new BitmapService(fileSystemMock.Object);
Size renderSize = new Size(100, 50);
var renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
var path = new Uri("//A_valid_path");
//Act
sut.SaveBitmapAsPngImage(path, renderBitmap);
//Assert
Mock.Get(mockedStream)
.Verify(_ => _.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()));
}
起订量测试结果:
BitmapServiceTest.BitmapService_Should_SaveBitmapAsPngImage threw exception: System.IO.IOException: Cannot read from the stream. ---> System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x88982F72 at System.Windows.Media.Imaging.BitmapEncoder.Save(Stream stream)
因此测试尝试实际使用该 Stream。
Class 测试中:
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using SystemInterface.IO;
using SystemWrapper.IO;
public interface IBitmapService
{
void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap);
}
public interface IBitmapEncoderFactory
{
BitmapEncoder CreateBitmapEncoder();
}
public class PngBitmapEncoderFactory : IBitmapEncoderFactory
{
public BitmapEncoder CreateBitmapEncoder()
{
return new PngBitmapEncoder();
}
}
public class BitmapService : IBitmapService
{
private readonly IFile _fileWrapper;
private readonly IBitmapEncoderFactory _bitmapEncoderFactory;
public BitmapService(IFile fileWrapper, IBitmapEncoderFactory bitmapEncoderFactory)
{
_fileWrapper = fileWrapper;
_bitmapEncoderFactory = bitmapEncoderFactory;
}
public void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap)
{
// Create a file stream for saving image
using (IStream outStream = _fileWrapper
.Open(path.LocalPath, FileMode.OpenOrCreate))
{
// Use bitmap encoder for our data
BitmapEncoder bitmapEncoder = _bitmapEncoderFactory.CreateBitmapEncoder();
// push the rendered bitmap to it
bitmapEncoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// The problem: Expects real Stream as parameter!!!
bitmapEncoder.Save(outStream.StreamInstance);
}
}
}
正如我在评论中建议的那样,如果使用模拟框架模拟 Stream
不适合您,请考虑通过包装 MemoryStream
、
public class MockedFileStream : MemoryStream {
protected override void Dispose(bool disposing) {
//base.Dispose(disposing);
//No Op fr the purposes of the test.
}
public override void Close() {
//base.Close();
//No Op fr the purposes of the test.
}
public void CustomDispose() {
base.Dispose(true);
GC.SuppressFinalize(this);
}
}
这将为您准备好所有管道。唯一的问题是您必须覆盖 Dispose
方法,因为流正在与 using
语句一起使用。
测试将更新为使用 "fake" 流
[TestMethod]
public void BitmapService_Should_SaveBitmapAsPngImage() {
//Arrange
var mockedStream = new MockedFileStream();
var fileSystemMock = new Mock<ImageDrawingCombiner3.IFileSystem>();
fileSystemMock
.Setup(_ => _.OpenOrCreateFileStream(It.IsAny<string>()))
.Returns(mockedStream);
var sut = new ImageDrawingCombiner3.BitmapService(fileSystemMock.Object);
Size renderSize = new Size(100, 50);
var renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
var path = new Uri("//A_valid_path");
//Act
sut.SaveBitmapAsPngImage(path, renderBitmap);
//Assert
mockedStream.Length.Should().BeGreaterThan(0); //data was written to it.
mockedStream.CustomDispose(); //done with stream
}
在我看来,真的没有必要模拟或抽象 PngBitmapEncoder
,因为这是一个实现问题。
非常感谢@NKosi 引领了正确的方向。 为了完整起见,我想 post NUnit、Rhino 模拟测试。
[Test]
public void ShouldSaveBitmapAsPngImage()
{
// Arrange
Uri pathToFile = new Uri("//A_valid_path");
IFileSystem fileSystemMock = MockRepository.GenerateStrictMock<IFileSystem>();
MockedFileStream mockedFileStream = new MockedFileStream();
fileSystemMock.Expect(x => x.OpenOrCreateFileStream(pathToFile.AbsolutePath))
.IgnoreArguments().Return(mockedFileStream);
BitmapService sut = new BitmapService(fileSystemMock);
Size renderSize = new Size(100, 50);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
// Act
sut.SaveBitmapAsPngImage(pathToFile, renderBitmap);
// Assert
// Was data was written to it?
Assert.That(mockedFileStream.Length, Is.GreaterThan(0));
mockedFileStream.CustomDispose(); //done with stream
}
并从服务中删除了 BitmapEncoder 抽象:
public class BitmapService : IBitmapService
{
private readonly IFileSystem _fileSystem;
public BitmapService(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
public void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap)
{
// Create a file stream for saving image
using (Stream outStream = _fileSystem.OpenOrCreateFileStream(path.LocalPath))
{
// Use bitmap encoder for our data
BitmapEncoder bitmapEncoder = new PngBitmapEncoder();
// push the rendered bitmap to it
bitmapEncoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
bitmapEncoder.Save(outStream);
}
}
}
我对整洁干净的解决方案非常满意。感谢@NKosi 和 Whosebug ;)