2012-08-15 20 views
22

Tengo el siguiente código para probar que cuando se pasa un cierto nombre a mi método, arroja una excepción SQL (hay una razón para eso, aunque suena un poco extraño).Moq y lanzando una SqlException

mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(), 
"Display Name 2", It.IsAny<string>())).Throws<SqlException>(); 

Sin embargo, esto no va a compilar porque el constructor de SqlException es interno:

'System.Data.SqlClient.SqlException' debe ser un tipo no abstracto con un constructor sin parámetros pública con el fin para utilizarlo como parámetro 'TException' en el tipo genérico o método 'Moq.Language.IThrows.Throws()'

Ahora, podría cambiar esta afirmar que debería lanzar Exception, pero eso no funcionaría para mí, porque mi método debería devolver un código de estado si es un SqlException y otro si es cualquier otra excepción. Eso es lo que prueba mi unidad.

¿Hay alguna manera de lograr esto sin cambiar la lógica del método que estoy probando o sin probar este escenario?

+1

posible duplicado de [Cómo lanzar una SqlException (necesidad de burlarse)] (http://stackoverflow.com/questions/1386962/how-to-throw-a-sqlexceptionneed-for-mocking) –

+1

Puede usar Reflection para acceda al método interno CreateException que le permite crear una SQLException. http://stackoverflow.com/questions/1259222/how-to-access-internal-class-using-reflection .... http://msdn.microsoft.com/en-us/library/ms229394(v=vs .100) .aspx ... entonces haz un lamda para crearlo y lanzarlo. –

Respuesta

33

Esto debería funcionar:

using System.Runtime.Serialization; 

var exception = FormatterServices.GetUninitializedObject(typeof(SqlException)) 
       as SqlException; 

mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(), "Display Name 2", 
        It.IsAny<string>())).Throws(exception); 

Sin embargo, el uso de GetUninitializedObject tiene esta advertencia:

Debido a que la nueva instancia del objeto se inicializa a cero y no hay constructores se ejecutan, el objeto podría no representa un estado que es considerado válido por ese objeto.

Si esto ocasiona algún problema, probablemente pueda crearlo utilizando una magia de reflexión más involucrada, pero de esta manera es probablemente la más simple (si funciona).

+0

No habría una manera de establecer el mensaje de excepción, ¿no? – bump

+0

@bump Podría ser posible a través de la reflexión, pero dependiendo de la estructura subyacente podría ser bastante difícil de hacer (es decir, obtener los campos de respaldo de las propiedades y establecerlas). No estoy seguro de qué configuración le da el mensaje, a menos que tenga una lógica diferente en ejecución según lo que indica el mensaje de excepción y debe probarlo. – docmanhattan

+0

De hecho, necesitaba probar eso.Pude hacerlo a través de la reflexión (y el constructor privado). Gracias. – bump

5

Acabo de intentar esto, y funcionó para mí:

private static void ThrowSqlException() 
{ 
    using (var cxn = new SqlConnection("Connection Timeout=1")) 
    { 
     cxn.Open(); 
    } 
} 

// ... 
mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>), 
        "Display Name 2", It.IsAny<string>())) 
       .Callback(() => ThrowSqlException()); 
+0

¿Qué ocurre si 'CreateAccount' devuelve nulo? –

+1

Probablemente quiera usar '.Callback' en cualquier caso, ahora que lo pienso. Actualización de respuesta. –

30

Si necesitas casos de prueba para los Number o Message propiedades de la excepción, se puede utilizar un constructor (que utiliza la reflexión) como esto :

using System; 
using System.Data.SqlClient; 
using System.Reflection; 

public class SqlExceptionBuilder 
{ 
    private int errorNumber; 
    private string errorMessage; 

    public SqlException Build() 
    { 
     SqlError error = this.CreateError(); 
     SqlErrorCollection errorCollection = this.CreateErrorCollection(error); 
     SqlException exception = this.CreateException(errorCollection); 

     return exception; 
    } 

    public SqlExceptionBuilder WithErrorNumber(int number) 
    { 
     this.errorNumber = number; 
     return this; 
    } 

    public SqlExceptionBuilder WithErrorMessage(string message) 
    { 
     this.errorMessage = message; 
     return this; 
    } 

    private SqlError CreateError() 
    { 
     // Create instance via reflection... 
     var ctors = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); 
     var firstSqlErrorCtor = ctors.FirstOrDefault(
      ctor => 
      ctor.GetParameters().Count() == 7); // Need a specific constructor! 
     SqlError error = firstSqlErrorCtor.Invoke(
      new object[] 
      { 
       this.errorNumber, 
       new byte(), 
       new byte(), 
       string.Empty, 
       string.Empty, 
       string.Empty, 
       new int() 
      }) as SqlError; 

     return error; 
    } 

    private SqlErrorCollection CreateErrorCollection(SqlError error) 
    { 
     // Create instance via reflection... 
     var sqlErrorCollectionCtor = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0]; 
     SqlErrorCollection errorCollection = sqlErrorCollectionCtor.Invoke(new object[] { }) as SqlErrorCollection; 

     // Add error... 
     typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(errorCollection, new object[] { error }); 

     return errorCollection; 
    } 

    private SqlException CreateException(SqlErrorCollection errorCollection) 
    { 
     // Create instance via reflection... 
     var ctor = typeof(SqlException).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0]; 
     SqlException sqlException = ctor.Invoke(
      new object[] 
      { 
       // With message and error collection... 
       this.errorMessage, 
       errorCollection, 
       null, 
       Guid.NewGuid() 
      }) as SqlException; 

     return sqlException; 
    } 
} 

entonces usted podría tener un simulacro de repositorio (por ejemplo) una excepción de esta manera:

using Moq; 

var sqlException = 
    new SqlExceptionBuilder().WithErrorNumber(50000) 
     .WithErrorMessage("Database exception occured...") 
     .Build(); 
var repoStub = new Mock<IRepository<Product>>(); // Or whatever... 
repoStub.Setup(stub => stub.GetById(1)) 
    .Throws(sqlException); 
+1

¡Gracias por esto! : D –

+0

@StephanRyer Tuve el código ... solo pensé en compartir – bump

+1

Eres un caballero y un erudito. Debes poner esto en Github para que pueda protagonizarlo. –

Cuestiones relacionadas