C# 模拟一个方法来抛出异常 (moq),但在其他方面表现得像模拟对象?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10323794/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mocking a method to throw an exception (moq), but otherwise act like the mocked object?
提问by Jeremy Holovacs
I have a Transferclass, simplified it looks like this:
我有一个Transfer课程,简化后看起来像这样:
public class Transfer
{
public virtual IFileConnection source { get; set; }
public virtual IFileConnection destination { get; set; }
public virtual void GetFile(IFileConnection connection,
string remoteFilename, string localFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void PutFile(IFileConnection connection,
string localFilename, string remoteFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void TransferFiles(string sourceName, string destName)
{
source = internalConfig.GetFileConnection("source");
destination = internalConfig.GetFileConnection("destination");
var tempName = Path.GetTempFileName();
GetFile(source, sourceName, tempName);
PutFile(destination, tempName, destName);
}
}
The simplified version of the IFileConnectioninterface looks like this:
IFileConnection界面的简化版本如下所示:
public interface IFileConnection
{
void Get(string remoteFileName, string localFileName);
void Put(string localFileName, string remoteFileName);
}
The real class is supposed to handle a System.IO.IOExceptionthat is thrown when the IFileConnectionconcrete classes loses connectivity with the remote, sending out emails and what not.
真正的类应该处理System.IO.IOException当IFileConnection具体类失去与远程的连接时抛出的a ,发送电子邮件等等。
I would like to use Moq to create a Transferclass, and use it as my concrete Transferclass in all properties and methods, except when the GetFilemethod is invoked - then I want it to throw a System.IO.IOExceptionand make sure the Transferclass handles it properly.
我想使用 Moq 创建一个Transfer类,并将其用作Transfer所有属性和方法中的具体类,除非GetFile调用该方法时- 然后我希望它抛出 aSystem.IO.IOException并确保Transfer该类正确处理它。
Am I using the right tool for the job? Am I going about this the right way? And how would I write the setup for that unit test for NUnit?
我是否使用了正确的工具来完成这项工作?我是否以正确的方式解决这个问题?我将如何为该单元测试编写设置NUnit?
采纳答案by Jeremy Holovacs
This is how I managed to do what I was trying to do:
这就是我设法做我想做的事情的方式:
[Test]
public void TransferHandlesDisconnect()
{
// ... set up config here
var methodTester = new Mock<Transfer>(configInfo);
methodTester.CallBase = true;
methodTester
.Setup(m =>
m.GetFile(
It.IsAny<IFileConnection>(),
It.IsAny<string>(),
It.IsAny<string>()
))
.Throws<System.IO.IOException>();
methodTester.Object.TransferFiles("foo1", "foo2");
Assert.IsTrue(methodTester.Object.Status == TransferStatus.TransferInterrupted);
}
If there is a problem with this method, I would like to know; the other answers suggest I am doing this wrong, but this was exactly what I was trying to do.
如果这个方法有问题,我想知道;其他答案表明我做错了,但这正是我想要做的。
回答by Ufuk Hac?o?ullar?
Here's how you can mock your FileConnection
这是你如何嘲笑你的 FileConnection
Mock<IFileConnection> fileConnection = new Mock<IFileConnection>(
MockBehavior.Strict);
fileConnection.Setup(item => item.Get(It.IsAny<string>,It.IsAny<string>))
.Throws(new IOException());
Then instantiate your Transfer class and use the mock in your method call
然后实例化您的 Transfer 类并在您的方法调用中使用模拟
Transfer transfer = new Transfer();
transfer.GetFile(fileConnection.Object, someRemoteFilename, someLocalFileName);
Update:
更新:
First of all you have to mock your dependencies only, not the class you are testing(Transfer class in this case). Stating those dependencies in your constructor make it easy to see what services your class needs to work. It also makes it possible to replace them with fakes when you are writing your unit tests. At the moment it's impossible to replace those properties with fakes.
首先,您必须仅模拟您的依赖项,而不是您正在测试的类(在本例中为 Transfer 类)。在构造函数中声明这些依赖关系可以很容易地看到你的类需要哪些服务来工作。它还可以在您编写单元测试时用伪造品替换它们。目前不可能用假货替换这些属性。
Since you are setting those properties using another dependency, I would write it like this:
由于您使用另一个依赖项设置这些属性,我会这样写:
public class Transfer
{
public Transfer(IInternalConfig internalConfig)
{
source = internalConfig.GetFileConnection("source");
destination = internalConfig.GetFileConnection("destination");
}
//you should consider making these private or protected fields
public virtual IFileConnection source { get; set; }
public virtual IFileConnection destination { get; set; }
public virtual void GetFile(IFileConnection connection,
string remoteFilename, string localFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void PutFile(IFileConnection connection,
string localFilename, string remoteFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void TransferFiles(string sourceName, string destName)
{
var tempName = Path.GetTempFileName();
GetFile(source, sourceName, tempName);
PutFile(destination, tempName, destName);
}
}
This way you can mock internalConfig and make it return IFileConnection mocks that does what you want.
通过这种方式,您可以模拟 internalConfig 并使其返回符合您要求的 IFileConnection 模拟。
回答by Jupaol
I think this is what you want, I already tested this code and works
我认为这就是你想要的,我已经测试了这段代码并且有效
The tools used are: (all these tools can be downloaded as Nuget packages)
使用的工具有:(所有这些工具都可以作为Nuget包下载)
http://fluentassertions.codeplex.com/
http://fluentassertions.codeplex.com/
http://autofixture.codeplex.com/
http://autofixture.codeplex.com/
https://nuget.org/packages/AutoFixture.AutoMoq
https://nuget.org/packages/AutoFixture.AutoMoq
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var myInterface = fixture.Freeze<Mock<IFileConnection>>();
var sut = fixture.CreateAnonymous<Transfer>();
myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
.Throws<System.IO.IOException>();
sut.Invoking(x =>
x.TransferFiles(
myInterface.Object,
It.IsAny<string>(),
It.IsAny<string>()
))
.ShouldThrow<System.IO.IOException>();
Edited:
编辑:
Let me explain:
让我解释:
When you write a test, you must know exactly what you want to test, this is called: "subject under test (SUT)", if my understanding is correctly, in this case your SUT is: Transfer
当你写一个测试时,你必须确切地知道你想要测试什么,这叫做:“被测对象(SUT)”,如果我的理解是正确的,在这种情况下你的 SUT 是: Transfer
So with this in mind, you should not mock your SUT, if you substitute your SUT, then you wouldn't be actually testing the real code
所以考虑到这一点,你不应该嘲笑你的 SUT,如果你替换你的 SUT,那么你实际上不会测试真正的代码
When your SUT has external dependencies (very common) then you need to substitute them in order to test in isolationyour SUT. When I say substitute I'm referring to use a mock, dummy, mock, etc depending on your needs
当你的SUT具有外部依赖性(很常见的),那么你需要以替代他们的测试中隔离您的SUT。当我说替代时,我指的是根据您的需要使用模拟、虚拟、模拟等
In this case your external dependency is IFileConnectionso you need to create mock for this dependency and configure it to throw the exception, then just call your SUT real method and assert your method handles the exception as expected
在这种情况下,您的外部依赖是IFileConnection这样的,因此您需要为此依赖创建模拟并将其配置为抛出异常,然后只需调用您的 SUT 真实方法并断言您的方法按预期处理异常
var fixture = new Fixture().Customize(new AutoMoqCustomization());: This linie initializes a new Fixture object (Autofixture library), this object is used to create SUT's without having to explicitly have to worry about the constructor parameters, since they are created automatically or mocked, in this case using Moqvar myInterface = fixture.Freeze<Mock<IFileConnection>>();: This freezes theIFileConnectiondependency. Freeze means that Autofixture will use always this dependency when asked, like a singleton for simplicity. But the interesting part is that we are creating a Mock of this dependency, you can use all the Moq methods, since this is a simple Moq objectvar sut = fixture.CreateAnonymous<Transfer>();: Here AutoFixture is creating the SUT for usmyInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>();Here you are configuring the dependency to throw an exception whenever theGetmethod is called, the rest of the methods from this interface are not being configured, therefore if you try to access them you will get an unexpected exceptionsut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>();: And finally, the time to test your SUT, this line uses the FluenAssertions library, and it just calls theTransferFilesreal method from the SUTand as parameters it receives the mockedIFileConnectionso whenever you call theIFileConnection.Getin the normal flow of your SUTTransferFilesmethod, the mocked object will be invoking throwing the configured exception and this is the time to assert that your SUT is handling correctly the exception, in this case, I am just assuring that the exception was thrown by using theShouldThrow<System.IO.IOException>()(from the FluentAssertions library)
var fixture = new Fixture().Customize(new AutoMoqCustomization());: 这个 linie 初始化一个新的 Fixture 对象(Autofixture 库),这个对象用于创建 SUT,而不必显式地担心构造函数参数,因为它们是自动创建或模拟的,在这种情况下使用 Moqvar myInterface = fixture.Freeze<Mock<IFileConnection>>();:这会冻结IFileConnection依赖项。冻结意味着 Autofixture 在被询问时将始终使用此依赖项,为简单起见,就像单例一样。但有趣的是,我们正在创建这个依赖项的 Mock,你可以使用所有的 Moq 方法,因为这是一个简单的 Moq 对象var sut = fixture.CreateAnonymous<Transfer>();:这里 AutoFixture 正在为我们创建 SUTmyInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>();在这里,您将依赖项配置为在Get调用方法时抛出异常,此接口中的其余方法未配置,因此如果您尝试访问它们,您将收到意外异常sut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>();:最后,是时候测试你的 SUT,这一行使用 FluenAssertions 库,它只是TransferFiles从 SUT调用真正的方法,并作为参数接收模拟,IFileConnection所以每当你IFileConnection.Get在 SUTTransferFiles方法的正常流程中调用时,模拟对象将调用抛出配置的异常,这是断言您的 SUT 正确处理异常的时候,在这种情况下,我只是确保使用ShouldThrow<System.IO.IOException>()(来自 FluentAssertions 库)抛出异常
References recommended:
推荐参考:
http://martinfowler.com/articles/mocksArentStubs.html
http://martinfowler.com/articles/mocksArentStubs.html
http://misko.hevery.com/code-reviewers-guide/
http://misko.hevery.com/code-reviewers-guide/
http://misko.hevery.com/presentations/
http://misko.hevery.com/presentations/
http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded
http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded
http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded
http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded

