내 프로젝트에서 몇 가지 예외를 테스트하려고하는데 내가 잡은 예외 중 하나는 SQlException
.
갈 수없는 것 new SqlException()
같아서 특히 어떻게 든 데이터베이스를 호출하지 않고 예외를 던질 수 있는지 잘 모르겠습니다 (그리고 이들은 단위 테스트이기 때문에 일반적으로 느리기 때문에 데이터베이스를 호출하지 않는 것이 좋습니다).
NUnit과 Moq를 사용하고 있지만 어떻게 가짜인지 잘 모르겠습니다.
모두 ADO.NET을 기반으로하는 것처럼 보이는 일부 답변에 대한 응답으로 Linq to Sql을 사용하고 있습니다. 그래서 그 물건은 무대 뒤와 같습니다.
@MattHamilton이 요청한 추가 정보 :
System.ArgumentException : Type to mock must be an interface or an abstract or non-sealed class.
at Moq.Mock`1.CheckParameters()
at Moq.Mock`1..ctor(MockBehavior behavior, Object[] args)
at Moq.Mock`1..ctor(MockBehavior behavior)
at Moq.Mock`1..ctor()
목업을 시도 할 때 첫 번째 줄에 게시
var ex = new Mock<System.Data.SqlClient.SqlException>();
ex.SetupGet(e => e.Message).Returns("Exception message");
답변
Linq to Sql을 사용하고 있으므로 NUnit 및 Moq를 사용하여 언급 한 시나리오를 테스트하는 샘플이 있습니다. 귀하의 DataContext에 대한 정확한 세부 사항과 귀하가 사용할 수있는 내용을 모르겠습니다. 필요에 맞게 편집하십시오.
사용자 정의 클래스로 DataContext를 래핑해야하며 Moq로 DataContext를 모의 할 수 없습니다. 봉인되어 있기 때문에 SqlException을 조롱 할 수 없습니다. 고유 한 Exception 클래스로 래핑해야합니다. 이 두 가지를 달성하는 것은 어렵지 않습니다.
테스트를 생성하여 시작하겠습니다.
[Test]
public void FindBy_When_something_goes_wrong_Should_handle_the_CustomSqlException()
{
var mockDataContextWrapper = new Mock<IDataContextWrapper>();
mockDataContextWrapper.Setup(x => x.Table<User>()).Throws<CustomSqlException>();
IUserResository userRespoistory = new UserRepository(mockDataContextWrapper.Object);
// Now, because we have mocked everything and we are using dependency injection.
// When FindBy is called, instead of getting a user, we will get a CustomSqlException
// Now, inside of FindBy, wrap the call to the DataContextWrapper inside a try catch
// and handle the exception, then test that you handled it, like mocking a logger, then passing it into the repository and verifying that logMessage was called
User user = userRepository.FindBy(1);
}
테스트를 구현하고 먼저 리포지토리 패턴을 사용하여 Linq에서 Sql로 호출을 래핑하겠습니다.
public interface IUserRepository
{
User FindBy(int id);
}
public class UserRepository : IUserRepository
{
public IDataContextWrapper DataContextWrapper { get; protected set; }
public UserRepository(IDataContextWrapper dataContextWrapper)
{
DataContextWrapper = dataContextWrapper;
}
public User FindBy(int id)
{
return DataContextWrapper.Table<User>().SingleOrDefault(u => u.UserID == id);
}
}
다음으로 IDataContextWrapper를 생성하면 주제에 대한 이 블로그 게시물 을 볼 수 있습니다 .
public interface IDataContextWrapper : IDisposable
{
Table<T> Table<T>() where T : class;
}
다음으로 CustomSqlException 클래스를 만듭니다.
public class CustomSqlException : Exception
{
public CustomSqlException()
{
}
public CustomSqlException(string message, SqlException innerException) : base(message, innerException)
{
}
}
다음은 IDataContextWrapper의 샘플 구현입니다.
public class DataContextWrapper<T> : IDataContextWrapper where T : DataContext, new()
{
private readonly T _db;
public DataContextWrapper()
{
var t = typeof(T);
_db = (T)Activator.CreateInstance(t);
}
public DataContextWrapper(string connectionString)
{
var t = typeof(T);
_db = (T)Activator.CreateInstance(t, connectionString);
}
public Table<TableName> Table<TableName>() where TableName : class
{
try
{
return (Table<TableName>) _db.GetTable(typeof (TableName));
}
catch (SqlException exception)
{
// Wrap the SqlException with our custom one
throw new CustomSqlException("Ooops...", exception);
}
}
// IDispoable Members
}
답변
리플렉션을 사용하여이 작업을 수행 할 수 있습니다. Microsoft가 변경할 때 유지 관리해야하지만 방금 테스트 한 결과 작동합니다.
public class SqlExceptionCreator
{
private static T Construct<T>(params object[] p)
{
var ctors = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
return (T)ctors.First(ctor => ctor.GetParameters().Length == p.Length).Invoke(p);
}
internal static SqlException NewSqlException(int number = 1)
{
SqlErrorCollection collection = Construct<SqlErrorCollection>();
SqlError error = Construct<SqlError>(number, (byte)2, (byte)3, "server name", "error message", "proc", 100);
typeof(SqlErrorCollection)
.GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(collection, new object[] { error });
return typeof(SqlException)
.GetMethod("CreateException", BindingFlags.NonPublic | BindingFlags.Static,
null,
CallingConventions.ExplicitThis,
new[] { typeof(SqlErrorCollection), typeof(string) },
new ParameterModifier[] { })
.Invoke(null, new object[] { collection, "7.0.0" }) as SqlException;
}
}
또한 중요 할 수있는 SqlException의 수를 제어 할 수 있습니다.
답변
이에 대한 해결책이 있습니다. 천재인지 광기인지 잘 모르겠습니다.
다음 코드는 새로운 SqlException을 생성합니다.
public SqlException MakeSqlException() {
SqlException exception = null;
try {
SqlConnection conn = new SqlConnection(@"Data Source=.;Database=GUARANTEED_TO_FAIL;Connection Timeout=1");
conn.Open();
} catch(SqlException ex) {
exception = ex;
}
return(exception);
}
그런 다음 그렇게 사용할 수 있습니다 (이 예제는 Moq를 사용합니다)
mockSqlDataStore
.Setup(x => x.ChangePassword(userId, It.IsAny<string>()))
.Throws(MakeSqlException());
리포지토리, 핸들러 및 컨트롤러에서 SqlException 오류 처리를 테스트 할 수 있습니다.
이제 가서 누워 야합니다.
답변
상황에 따라 일반적으로 ConstructorInfo 를 호출 하는 것 보다 GetUninitializedObject 를 선호합니다 . 생성자를 호출하지 않는다는 점만 알고 있어야합니다. MSDN 설명에서 “개체의 새 인스턴스가 0으로 초기화되고 생성자가 실행되지 않기 때문에 개체가 유효한 것으로 간주되는 상태를 나타내지 않을 수 있습니다. 그 개체에 의해. ” 그러나 특정 생성자의 존재에 의존하는 것보다 덜 깨지기 쉽다고 말하고 싶습니다.
[TestMethod]
[ExpectedException(typeof(System.Data.SqlClient.SqlException))]
public void MyTestMethod()
{
throw Instantiate<System.Data.SqlClient.SqlException>();
}
public static T Instantiate<T>() where T : class
{
return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(T)) as T;
}
답변
편집하다Ouch : SqlException이 봉인되어 있다는 것을 몰랐습니다. 추상 클래스 인 DbException을 조롱했습니다.
새 SqlException을 만들 수는 없지만 SqlException이 파생 된 DbException을 모의 처리 할 수 있습니다. 이 시도:
var ex = new Mock<DbException>();
ex.ExpectGet(e => e.Message, "Exception message");
var conn = new Mock<SqlConnection>();
conn.Expect(c => c.Open()).Throws(ex.Object);
따라서 메서드가 연결을 열려고 할 때 예외가 발생합니다.
Message
모의 예외에 대한 속성 이외의 다른 것을 읽으려면 해당 속성에 대한 “가져 오기”를 기대 (또는 Moq 버전에 따라 설정)하는 것을 잊지 마십시오.
답변
이것이 도움이되는지 확실하지 않지만이 사람을 위해 일한 것 같습니다 (매우 영리함).
try
{
SqlCommand cmd =
new SqlCommand("raiserror('Manual SQL exception', 16, 1)",DBConn);
cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
string msg = ex.Message; // msg = "Manual SQL exception"
}
위치 :
http://smartypeeps.blogspot.com/2006/06/how-to-throw-sqlexception-in-c.html
답변
이것은 작동합니다.
SqlConnection bogusConn =
new SqlConnection("Data Source=myServerAddress;Initial
Catalog=myDataBase;User Id=myUsername;Password=myPassword;");
bogusConn.Open();
예외가 발생하기 전에 약간의 시간이 걸리므로 이것이 더 빨리 작동 할 것이라고 생각합니다.
SqlCommand bogusCommand = new SqlCommand();
bogusCommand.ExecuteScalar();
Hacks-R-Us가 제공하는 코드.
최신 정보 : 아니요, 두 번째 방법은 SqlException이 아닌 ArgumentException을 throw합니다.
업데이트 2 : 이것은 훨씬 빠르게 작동합니다 (SqlException이 1 초 이내에 발생합니다).
SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial
Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection
Timeout=1");
bogusConn.Open();
