I have the following code to test that when a certain name is passed to my method, it throws a SQL exception (there is reason to that one, although it sounds a little odd).
mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(),
"Display Name 2", It.IsAny<string>())).Throws<SqlException>();
However, this won't compile because SqlException's constructor is internal:
'System.Data.SqlClient.SqlException' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TException' in the generic type or method 'Moq.Language.IThrows.Throws()'
Now, I could change this to state that it should throw Exception
, but that wouldn't work for me, because my method should return one status code if it is a SqlException
and another if it is any other exception. That's what my unit test is testing.
Is there any way to achieve this without either changing the logic of the method I'm testing, or not testing this scenario?
This should work:
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);
However, using GetUninitializedObject
has this caveat:
Because the new instance of the object is initialized to zero and no constructors are run, the object might not represent a state that is regarded as valid by that object.
If this causes any problems, you can probably create it using some more involved reflection magic but this way is probably the simplest (if it works).
If you need test cases for the Number
or Message
properties of the exception, you could use a builder (which uses reflection) like this:
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;
}
}
Then you could have a repository mock (for instance) throw an exception like this:
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);
I just tried this out, and it worked for me:
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());
For me to produce an SqlException
with a message it was the simplest way using the Uninitialized Object method:
const string sqlErrorMessage = "MyCustomMessage";
var sqlException = FormatterServices.GetUninitializedObject(typeof(SqlException)) as SqlException;
var messageField = typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance);
messageField.SetValue(sqlException, sqlErrorMessage);
来源:https://stackoverflow.com/questions/11976996/moq-and-throwing-a-sqlexception