propias - que tipos de excepciones existen en c#
¿Se burla de un método para lanzar una excepción(moq), pero actúa como el objeto burlado? (3)
Tengo una clase de Transfer
, simplificado, se ve así:
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);
}
}
La versión simplificada de la interfaz IFileConnection
tiene este aspecto:
public interface IFileConnection
{
void Get(string remoteFileName, string localFileName);
void Put(string localFileName, string remoteFileName);
}
Se supone que la clase real maneja una System.IO.IOException
que se lanza cuando las clases concretas de IFileConnection
pierden conectividad con el control remoto, el envío de correos electrónicos y otras IFileConnection
.
Me gustaría usar Moq para crear una clase de Transfer
, y usarla como mi clase de Transfer
concreta en todas las propiedades y métodos, excepto cuando se invoca el método GetFile
- entonces quiero que arroje una System.IO.IOException
y asegúrese de que Transfer
clase de Transfer
maneja correctamente.
¿Estoy usando la herramienta correcta para el trabajo? ¿Voy por esto de la manera correcta? ¿Y cómo escribiría la configuración para esa prueba unitaria para NUnit
?
Así es como logré hacer lo que estaba tratando de hacer:
[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);
}
Si hay un problema con este método, me gustaría saber; las otras respuestas sugieren que estoy haciendo esto mal, pero esto era exactamente lo que estaba tratando de hacer.
Así es como puedes FileConnection
tu FileConnection
Mock<IFileConnection> fileConnection = new Mock<IFileConnection>(
MockBehavior.Strict);
fileConnection.Setup(item => item.Get(It.IsAny<string>,It.IsAny<string>))
.Throws(new IOException());
A continuación, crea una instancia de tu clase de transferencia y utiliza el simulacro en tu llamada al método
Transfer transfer = new Transfer();
transfer.GetFile(fileConnection.Object, someRemoteFilename, someLocalFileName);
Actualizar:
En primer lugar, tiene que burlarse de sus dependencias solamente, no de la clase que está probando (Clase de transferencia en este caso). Al indicar esas dependencias en su constructor, es más fácil ver qué servicios necesita su clase para funcionar. También hace posible reemplazarlos con falsificaciones cuando está escribiendo las pruebas de su unidad. Por el momento, es imposible reemplazar esas propiedades con falsificaciones.
Como está configurando esas propiedades usando otra dependencia, lo escribiría así:
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);
}
}
De esta forma puede burlarse de InternalConfig y hacer que regrese. IFileConnection se burla de que hace lo que quiere.
Creo que esto es lo que quieres, ya probé este código y funciona
Las herramientas utilizadas son: (todas estas herramientas se pueden descargar como paquetes de Nuget)
http://fluentassertions.codeplex.com/
http://autofixture.codeplex.com/
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>();
Editado:
Dejame explicar:
Cuando escribe una prueba, debe saber exactamente qué quiere probar, esto se llama: "sujeto bajo prueba (SUT)", si mi entendimiento es correcto, en este caso su SUT es: Transfer
Entonces, con esto en mente, no deberías burlarte de tu SUT, si sustituyes tu SUT, entonces no estarías realmente probando el código real.
Cuando su SUT tiene dependencias externas (muy comunes), debe sustituirlas para probar aisladamente su SUT. Cuando digo sustituto me refiero a usar un simulacro, simulado, simulacro, etc. según sus necesidades
En este caso, su dependencia externa es IFileConnection
por lo que necesita crear un simulacro para esta dependencia y configurarlo para lanzar la excepción, luego solo llame a su método SUT real y afirme que su método maneja la excepción como se esperaba
var fixture = new Fixture().Customize(new AutoMoqCustomization());
: Este linie inicializa un nuevo objeto Fixture (biblioteca Autofixture), este objeto se usa para crear SUT sin tener que preocuparse explícitamente por los parámetros del constructor, ya que se crean automáticamente o se burlan, en este caso usando Moqvar myInterface = fixture.Freeze<Mock<IFileConnection>>();
: Esto congela la dependenciaIFileConnection
. Congelar significa que Autofixture usará siempre esta dependencia cuando se le pida, como un singleton por simplicidad. Pero la parte interesante es que estamos creando un simulacro de esta dependencia, puedes usar todos los métodos Moq, ya que este es un simple objeto Moqvar sut = fixture.CreateAnonymous<Transfer>();
: Aquí AutoFixture está creando el SUT para nosotrosmyInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>();
Aquí está configurando la dependencia para lanzar una excepción cada vez que se llama al métodoGet
, el resto de los métodos de esta interfaz no se están configurando, por lo tanto, si intenta acceder a ellos, obtendrá una excepción inesperadasut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>();
: Y finalmente, el tiempo para probar su SUT, esta línea usa la biblioteca FluenAsertions, y solo llama al método realTransferFiles
del SUT y como parámetros recibe elIFileConnection
así que siempre que llame alIFileConnection.Get
en el flujo normal de su método SUTTransferFiles
, el objeto burlado invocará lanzando la excepción configurada y este es el momento de afirmar que su SUT maneja correctamente la excepción, en este caso, solo estoy asegurando que la excepción fue lanzada usando el sistemaShouldThrow<System.IO.IOException>()
(de la biblioteca FluentAsertions)
Referencias recomendadas:
http://martinfowler.com/articles/mocksArentStubs.html
http://misko.hevery.com/code-reviewers-guide/
http://misko.hevery.com/presentations/
http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded
http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded