จะโยน SqlException เมื่อจำเป็นสำหรับการจำลองและการทดสอบหน่วยได้อย่างไร?


86

SQlExceptionฉันพยายามที่จะทดสอบข้อยกเว้นบางประการในโครงการของฉันและเป็นหนึ่งในข้อยกเว้นที่ผมจับเป็น

ดูเหมือนว่าคุณจะไปไม่ได้new SqlException()ดังนั้นฉันจึงไม่แน่ใจว่าฉันจะทิ้งข้อยกเว้นได้อย่างไรโดยเฉพาะอย่างยิ่งโดยไม่ต้องเรียกฐานข้อมูล (และเนื่องจากเป็นการทดสอบหน่วยจึงมักไม่แนะนำให้เรียกฐานข้อมูลเนื่องจากมันช้า)

ฉันใช้ NUnit และ Moq แต่ฉันไม่แน่ใจว่าจะปลอมได้อย่างไร

การตอบกลับคำตอบบางส่วนที่ดูเหมือนว่าทั้งหมดจะขึ้นอยู่กับ ADO.NET โปรดทราบว่าฉันใช้ Linq กับ 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");

คุณถูก. ฉันได้อัปเดตการตอบกลับของฉันแล้ว แต่ตอนนี้อาจยังไม่เป็นประโยชน์ DbException อาจเป็นข้อยกเว้นที่ดีกว่าที่จะจับได้ดังนั้นควรพิจารณาให้ดี
Matt Hamilton

คำตอบที่ใช้งานได้จริงจะสร้างข้อความแสดงข้อยกเว้นที่หลากหลาย การกำหนดประเภทที่คุณต้องการอาจเป็นประโยชน์ เช่น "ฉันต้องการ SqlException ที่มีหมายเลขข้อยกเว้น 18487 ซึ่งระบุว่ารหัสผ่านที่ระบุหมดอายุแล้ว" ดูเหมือนว่าโซลูชันดังกล่าวจะเหมาะสมกว่าสำหรับการทดสอบหน่วย
Mike Christian

คำตอบ:


9

เนื่องจากคุณใช้ Linq to Sql นี่คือตัวอย่างการทดสอบสถานการณ์ที่คุณกล่าวถึงโดยใช้ NUnit และ Moq ฉันไม่ทราบรายละเอียดที่แน่นอนของ DataContext ของคุณและสิ่งที่คุณมีอยู่ในนั้น แก้ไขตามความต้องการของคุณ

คุณจะต้องห่อ DataContext ด้วยคลาสที่กำหนดเองคุณไม่สามารถจำลอง DataContext ด้วย Moq ได้ คุณไม่สามารถเยาะเย้ย 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
}

92

คุณสามารถทำได้ด้วยการไตร่ตรองคุณจะต้องรักษาไว้เมื่อ 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;
    }
}      

นอกจากนี้ยังช่วยให้คุณควบคุม Number of the SqlException ซึ่งอาจมีความสำคัญ


2
วิธีนี้ใช้ได้ผลคุณเพียงแค่ต้องเจาะจงมากขึ้นด้วยวิธีการ CreateException ที่คุณต้องการเนื่องจากมีสองโอเวอร์โหลด เปลี่ยนการเรียก GetMethod เป็น: .GetMethod ("CreateException", BindingFlags.NonPublic | BindingFlags.Static, null, CallingConventions.ExplicitThis, new [] {typeof (SqlErrorCollection), typeof (string)}, new ParameterModifier [] {}) และ ได้ผล
Erik Nordenhök

เหมาะสำหรับฉัน ยอดเยี่ยม.
Nick Patsaris

4
กลายเป็นส่วนสำคัญด้วยการแก้ไขจากความคิดเห็น gist.github.com/timabell/672719c63364c497377f - ขอบคุณทุกคนที่ช่วยหาทางออกจากที่มืดอันมืดมิดนี้
Tim Abell

2
เวอร์ชันจาก Ben J Anderson อนุญาตให้คุณระบุข้อความนอกเหนือจากรหัสข้อผิดพลาด gist.github.com/benjanderson/07e13d9a2068b32c2911
Tony

10
เพื่อให้สิ่งนี้ทำงานกับ dotnet-core 2.0 ให้เปลี่ยนบรรทัดที่สองในNewSqlExceptionวิธีการอ่าน:SqlError error = Construct<SqlError>(number, (byte)2, (byte)3, "server name", "error message", "proc", 100, null);
Chuck Spencer

75

ฉันมีวิธีแก้ปัญหานี้ ฉันไม่แน่ใจว่าเป็นอัจฉริยะหรือบ้า

รหัสต่อไปนี้จะสร้าง 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 ในที่เก็บตัวจัดการและคอนโทรลเลอร์ของคุณ

ตอนนี้ฉันต้องไปและนอนลง


10
ทางออกที่ยอดเยี่ยม! ฉันทำการแก้ไขหนึ่งครั้งเพื่อประหยัดเวลาในการรอการเชื่อมต่อ:new SqlConnection(@"Data Source=.;Database=GUARANTEED_TO_FAIL;Connection Timeout=1")
Joanna Derks

2
ฉันชอบอารมณ์ที่คุณเพิ่มเข้ามาในคำตอบของคุณ ฮ่า ๆ ขอบคุณสำหรับการแก้ปัญหานี้ มันเป็นเกมง่ายๆและฉันไม่รู้ว่าทำไมฉันถึงไม่คิดเรื่องนี้ในตอนแรก ขอบคุณอีกครั้ง
pqsk

1
ทางออกที่ดีตรวจสอบให้แน่ใจว่าคุณไม่มีฐานข้อมูลชื่อ GUARANTEED_TO_FAIL ในเครื่องของคุณ)
Amit G

ตัวอย่างที่ดีของ KISS
Lup

นี่เป็นวิธีแก้ปัญหาอย่างชาญฉลาด
Mykhailo Seniutovych

22

ขึ้นอยู่กับสถานการณ์ฉันมักจะชอบGetUninitializedObjectเพื่อเรียกใช้ ConstructorInfo คุณต้องทราบว่ามันไม่ได้เรียกตัวสร้าง - จากข้อสังเกตของ MSDN: "เนื่องจากอินสแตนซ์ใหม่ของอ็อบเจ็กต์เริ่มต้นเป็นศูนย์และไม่มีการรันตัวสร้างอ็อบเจ็กต์อาจไม่แสดงสถานะที่ถือว่าถูกต้อง โดยวัตถุนั้น " แต่ฉันว่ามันเปราะน้อยกว่าการอาศัยการมีอยู่ของตัวสร้างบางตัว

[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;
}

4
สิ่งนี้ใช้ได้ผลสำหรับฉันและเพื่อตั้งค่าข้อความยกเว้นเมื่อคุณมีวัตถุ:typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, "my custom sql message");
Phil Cooper

8
ฉันขยายสิ่งนี้เพื่อสะท้อนถึง ErrorMessage และ ErrorCode gist.github.com/benjanderson/07e13d9a2068b32c2911
Ben Anderson

13

แก้ไขอุ๊ย: ฉันไม่รู้ว่า SqlException ถูกปิดผนึก ฉันล้อเลียน DbException ซึ่งเป็นคลาสนามธรรม

คุณไม่สามารถสร้าง SqlException ใหม่ได้ แต่คุณสามารถจำลอง DbException ซึ่ง SqlException มาจาก ลองสิ่งนี้:

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) "รับ" ในคุณสมบัติเหล่านั้น


คุณควรเพิ่มความคาดหวังสำหรับ "จำนวน" ที่ช่วยให้คุณที่จะคิดออกสิ่งที่ประเภทของข้อยกเว้นคือ (หยุดชะงักหมดเวลา ฯลฯ )
แซม Saffron

อืมเวลาที่คุณใช้ linq เป็น sql ล่ะ? ฉันไม่ได้เปิดจริง (มันทำเพื่อฉัน)
chobo2

หากคุณกำลังใช้ Moq อาจเป็นไปได้ว่าคุณกำลังล้อเลียนการทำงานของฐานข้อมูลบางประเภท ตั้งค่าที่จะโยนเมื่อสิ่งนั้นเกิดขึ้น
Matt Hamilton

ดังนั้นในการดำเนินการจริง (วิธีการจริงที่จะเรียกฐานข้อมูล)?
chobo2

คุณกำลังล้อเลียนพฤติกรรมฐานข้อมูลของคุณหรือไม่? เช่นล้อเลียนคลาส DataContext ของคุณหรืออะไร? การดำเนินการใด ๆ จะทำให้เกิดข้อยกเว้นนี้หากการดำเนินการฐานข้อมูลส่งกลับข้อผิดพลาด
Matt Hamilton

4

ไม่แน่ใจว่าจะช่วยได้ไหม แต่ดูเหมือนว่าจะได้ผลสำหรับคน ๆ นี้ (ค่อนข้างฉลาด)

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 ที่เปิดอยู่เพื่อที่จะได้รับ SqlException
MusiGenesis

ฉันใช้ linq กับ sql ดังนั้นฉันจึงไม่ทำสิ่งนี้ เบื้องหลังทั้งหมด
chobo2

2

สิ่งนี้ควรใช้งานได้:

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 นำรหัสมาให้คุณ

อัปเดต : ไม่วิธีที่สองพ่น ArgumentException ไม่ใช่ SqlException

อัปเดต 2 : สิ่งนี้ทำงานได้เร็วขึ้นมาก (SqlException ถูกโยนในเวลาน้อยกว่าหนึ่งวินาที):

SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial
    Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection
    Timeout=1");
bogusConn.Open();

นี่เป็นการใช้งานของฉันเองก่อนที่ฉันจะเจอหน้า SU นี้โดยมองหาวิธีอื่นเนื่องจากไม่สามารถยอมรับการหมดเวลาได้ การอัปเดต 2 ของคุณดี แต่ยังเหลืออีก 1 วินาที ไม่ดีสำหรับชุดทดสอบหน่วยเนื่องจากไม่ปรับขนาด
Jon Davis

2

ฉันสังเกตเห็นว่าคำถามของคุณมีอายุ 1 ปี แต่สำหรับบันทึกฉันต้องการเพิ่มโซลูชันที่ฉันค้นพบเมื่อเร็ว ๆ นี้โดยใช้ microsoft Moles (คุณสามารถดูข้อมูลอ้างอิงได้ที่นี่Microsoft Moles )

เมื่อคุณโมลเนมสเปซ System.Data แล้วคุณสามารถจำลองข้อยกเว้นของ SQL บน SqlConnection.Open () ได้ดังนี้:

//Create a delegate for the SqlConnection.Open method of all instances
        //that raises an error
        System.Data.SqlClient.Moles.MSqlConnection.AllInstances.Open =
            (a) =>
            {
                SqlException myException = new System.Data.SqlClient.Moles.MSqlException();
                throw myException;
            };

ฉันหวังว่านี่จะช่วยคนที่ตอบคำถามนี้ได้ในอนาคต


1
แม้จะมีคำตอบที่ล่าช้า แต่นี่อาจเป็นวิธีแก้ปัญหาที่สะอาดที่สุดโดยเฉพาะอย่างยิ่งหากคุณใช้โมลเพื่อจุดประสงค์อื่นอยู่แล้ว
Amandalishus

1
คุณต้องใช้ Moles framework เพื่อให้สามารถใช้งานได้ ไม่เหมาะอย่างยิ่งเมื่อใช้ MOQ อยู่แล้ว โซลูชันนี้เป็นการอ้อมสายไปยัง. NET Framework คำตอบโดย @ default.kramer เหมาะสมกว่า Moles เปิดตัวใน Visual Studio 2012 Ultimate ในชื่อ "Fakes" และต่อมาใน VS 2012 Premium ผ่านการอัปเดต 2 ฉันทุกคนใช้เฟรมเวิร์ก Fakes แต่ยึดติดกับเฟรมเวิร์กการเยาะเย้ยทีละเฟรมเพื่อประโยชน์ของสิ่งเหล่านี้ หลังจากที่คุณ. ;)
Mike Christian

2

ฉันขอแนะนำให้ใช้วิธีนี้

    /// <summary>
    /// Method to simulate a throw SqlException
    /// </summary>
    /// <param name="number">Exception number</param>
    /// <param name="message">Exception message</param>
    /// <returns></returns>
    public static SqlException CreateSqlException(int number, string message)
    {
        var collectionConstructor = typeof(SqlErrorCollection)
            .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, //visibility
                null, //binder
                new Type[0],
                null);
        var addMethod = typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance);
        var errorCollection = (SqlErrorCollection)collectionConstructor.Invoke(null);
        var errorConstructor = typeof(SqlError).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null,
            new[]
            {
                typeof (int), typeof (byte), typeof (byte), typeof (string), typeof(string), typeof (string),
                typeof (int), typeof (uint)
            }, null);
        var error =
            errorConstructor.Invoke(new object[] { number, (byte)0, (byte)0, "server", "errMsg", "proccedure", 100, (uint)0 });
        addMethod.Invoke(errorCollection, new[] { error });
        var constructor = typeof(SqlException)
            .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, //visibility
                null, //binder
                new[] { typeof(string), typeof(SqlErrorCollection), typeof(Exception), typeof(Guid) },
                null); //param modifiers
        return (SqlException)constructor.Invoke(new object[] { message, errorCollection, new DataException(), Guid.NewGuid() });
    }

จากคิวการตรวจสอบ : ฉันขอให้คุณโปรดเพิ่มบริบทเพิ่มเติมเกี่ยวกับคำตอบของคุณ คำตอบที่ใช้รหัสเท่านั้นเป็นเรื่องยากที่จะเข้าใจ จะช่วยให้ผู้ถามและผู้อ่านในอนาคตทั้งสองอย่างหากคุณสามารถเพิ่มข้อมูลเพิ่มเติมในโพสต์ของคุณได้
RBT

คุณอาจต้องการเพิ่มข้อมูลนี้โดยแก้ไขโพสต์เอง โพสต์เป็นสถานที่ที่ดีกว่าการแสดงความคิดเห็นเพื่อรักษาข้อมูลที่เกี่ยวข้องเกี่ยวกับคำตอบ
RBT

สิ่งนี้ใช้ไม่ได้อีกต่อไปเนื่องจากSqlExceptionไม่มีตัวสร้างและerrorConstructorจะเป็นโมฆะ
Emad

@ อีมัดคุณใช้อะไรในการเอาชนะปัญหา?
Sasuke Uchiha

2

วิธีแก้ปัญหาเหล่านี้รู้สึกท้องอืด

ctor เป็นภายในใช่

(โดยไม่ต้องใช้การสะท้อนเป็นวิธีที่ง่ายที่สุดในการสร้างข้อยกเว้นนี้อย่างแท้จริง ....

   instance.Setup(x => x.MyMethod())
            .Callback(() => new SqlConnection("Server=pleasethrow;Database=anexception;Connection Timeout=1").Open());

Perphaps มีอีกวิธีหนึ่งที่ไม่ต้องใช้เวลาในการขว้าง 1 วินาที


ฮะ ... ง่ายมากไม่รู้ทำไมไม่คิดแบบนี้ ... สมบูรณ์แบบไม่ยุ่งยากและทำแบบนี้ได้ทุกที่
hal9000

แล้วการตั้งค่าข้อความและรหัสข้อผิดพลาดล่ะ? ดูเหมือนว่าโซลูชันของคุณไม่อนุญาตให้ทำเช่นนั้น
Sasuke Uchiha

@ ซาสึเกะอุจิวะไม่แน่นะ วิธีแก้ปัญหาอื่น ๆ แต่ถ้าคุณเพียงแค่ต้องการยกเว้นประเภทนี้ต้องการหลีกเลี่ยงการสะท้อนและไม่เขียนโค้ดมากคุณสามารถใช้วิธีนี้ได้
Billy Jake O'Connor

1

(น่าจะช้าไป 6 เดือนหวังว่านี่จะไม่ถือเป็นการตายที่ฉันมาที่นี่เพื่อค้นหาวิธีโยน SqlCeException จากการเยาะเย้ย)

หากคุณเพียงแค่ต้องการทดสอบโค้ดที่จัดการกับข้อยกเว้นวิธีแก้ปัญหาที่ง่ายเป็นพิเศษคือ:

public void MyDataMethod(){
    try
    {
        myDataContext.SubmitChanges();
    }
    catch(Exception ex)
    {
        if(ex is SqlCeException || ex is TestThrowableSqlCeException)
        {
            // handle ex
        }
        else
        {
            throw;
        }
    }
}



public class TestThrowableSqlCeException{
   public TestThrowableSqlCeException(string message){}
   // mimic whatever properties you needed from the SqlException:
}

var repo = new Rhino.Mocks.MockReposity();
mockDataContext = repo.StrictMock<IDecoupleDataContext>();
Expect.Call(mockDataContext.SubmitChanges).Throw(new TestThrowableSqlCeException());

1

จากคำตอบอื่น ๆ ทั้งหมดฉันได้สร้างโซลูชันต่อไปนี้:

    [Test]
    public void Methodundertest_ExceptionFromDatabase_Logs()
    {
        _mock
            .Setup(x => x.MockedMethod(It.IsAny<int>(), It.IsAny<string>()))
            .Callback(ThrowSqlException);

        _service.Process(_batchSize, string.Empty, string.Empty);

        _loggermock.Verify(x => x.Error(It.IsAny<string>(), It.IsAny<SqlException>()));
    }

    private static void ThrowSqlException() 
    {
        var bogusConn =
            new SqlConnection(
                "Data Source=localhost;Initial Catalog = myDataBase;User Id = myUsername;Password = myPassword;Connection Timeout = 1");
        bogusConn.Open();
    }

1

มันเก่าจริงๆและมีคำตอบที่ดีอยู่ที่นี่ ฉันใช้ Moq และฉันไม่สามารถล้อเลียนคลาส Abstract ได้และไม่ต้องการใช้การสะท้อนดังนั้นฉันจึงสร้าง Exception ของตัวเองที่ได้มาจาก DbException ดังนั้น:

public class MockDbException : DbException {
  public MockDbException(string message) : base (message) {}
}   

เห็นได้ชัดว่าหากคุณต้องการเพิ่ม InnerException หรืออะไรก็ตามให้เพิ่มอุปกรณ์ประกอบฉากตัวสร้าง ฯลฯ

จากนั้นในการทดสอบของฉัน:

MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));

สิ่งนี้จะช่วยทุกคนที่ใช้ Moq ขอบคุณสำหรับทุกคนที่โพสต์ที่นี่ที่ทำให้ฉันได้รับคำตอบ


เมื่อคุณไม่ต้องการอะไรที่เฉพาะเจาะจงใน SqlException วิธีนี้ใช้ได้ดีจริงๆ
Ralph Willgoss

0

คุณสามารถใช้การสะท้อนเพื่อสร้างวัตถุ SqlException ในการทดสอบ:

        ConstructorInfo errorsCi = typeof(SqlErrorCollection).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[]{}, null);
        var errors = errorsCi.Invoke(null);

        ConstructorInfo ci = typeof(SqlException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(SqlErrorCollection) }, null);
        var sqlException = (SqlException)ci.Invoke(new object[] { "Exception message", errors });

สิ่งนี้จะไม่ทำงาน SqlException ไม่มีตัวสร้าง คำตอบจาก @ default.kramer ทำงานได้อย่างถูกต้อง
Mike Christian

1
@MikeChristian มันใช้งานได้ถ้าคุณใช้ constuctor ที่มีอยู่เช่นprivate SqlException(string message, SqlErrorCollection errorCollection, Exception innerException, Guid conId)
Shaun Wilde

0

หากคุณกำลังใช้ Microsoft.Data.SqlClient nuget ใหม่คุณสามารถใช้วิธีช่วยเหลือนี้:

public static class SqlExceptionCreator
{
    public static SqlException Create(int number)
    {
        Exception? innerEx = null;
        var c = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        SqlErrorCollection errors = (c[0].Invoke(null) as SqlErrorCollection)!;
        var errorList = (errors.GetType().GetField("_errors", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(errors) as List<object>)!;
        c = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        var nineC = c.FirstOrDefault(f => f.GetParameters().Length == 9)!;
        SqlError sqlError = (nineC.Invoke(new object?[] { number, (byte)0, (byte)0, "", "", "", (int)0, (uint)0, innerEx}) as SqlError)!;
        errorList.Add(sqlError);
        SqlException ex = (Activator.CreateInstance(typeof(SqlException), BindingFlags.NonPublic | BindingFlags.Instance, null, new object?[] { "test", errors,
            innerEx, Guid.NewGuid() }, null) as SqlException)!;
        return ex;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.