Tại sao tôi nhận được một Ngoại lệ với thông báo Thiết lập không hợp lệ trên một thành viên không ảo (có thể ghi đè trong VB) không phải là ảo?


176

Tôi có một bài kiểm tra đơn vị trong đó tôi phải giả định một phương thức không ảo trả về kiểu bool

public class XmlCupboardAccess
{
    public bool IsDataEntityInXmlCupboard(string dataId,
                                          out string nameInCupboard,
                                          out string refTypeInCupboard,
                                          string nameTemplate = null)
    {
        return IsDataEntityInXmlCupboard(_theDb, dataId, out nameInCupboard, out refTypeInCupboard, nameTemplate);
    }
}

Vì vậy, tôi có một đối tượng giả của XmlCupboardAccesslớp và tôi đang cố gắng thiết lập giả cho phương thức này trong trường hợp thử nghiệm của mình như dưới đây

[TestMethod]
Public void Test()
{
    private string temp1;
    private string temp2;
    private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();
    _xmlCupboardAccess.Setup(x => x.IsDataEntityInXmlCupboard(It.IsAny<string>(), out temp1, out temp2, It.IsAny<string>())).Returns(false); 
    //exception is thrown by this line of code
}

Nhưng dòng này ném ngoại lệ

Invalid setup on a non-virtual (overridable in VB) member: 
x => x.IsDataEntityInXmlCupboard(It.IsAny<String>(), .temp1, .temp2, 
It.IsAny<String>())

Bất kỳ đề nghị làm thế nào để có được xung quanh ngoại lệ này?


Những gì trong bài kiểm tra của bạn phụ thuộc vào XmlCupboardAccess?
Preston Guillot

9
nó đơn giản .. bạn cần đánh dấu nó virtual. Moq không thể chế nhạo một loại cụ thể mà nó không thể ghi đè.
Simon Whitehead

Câu trả lời:


265

Moq không thể chế nhạo các phương thức phi ảo và các lớp niêm phong. Trong khi chạy thử nghiệm bằng cách sử dụng đối tượng giả, Moq thực sự tạo ra một loại proxy trong bộ nhớ kế thừa từ "XmlCupboardAccess" của bạn và ghi đè các hành vi mà bạn đã thiết lập trong phương thức "SetUp". Và như bạn đã biết trong C #, bạn chỉ có thể ghi đè lên một cái gì đó nếu nó được đánh dấu là ảo không phải là trường hợp của Java. Java giả định mọi phương thức không tĩnh là ảo theo mặc định.

Một điều nữa tôi tin rằng bạn nên xem xét là giới thiệu một giao diện cho "CupboardAccess" của bạn và bắt đầu chế giễu giao diện thay thế. Nó sẽ giúp bạn tách mã của bạn và có lợi ích trong thời gian dài hơn.

Cuối cùng, có các khung như: TypeMockJustMock hoạt động trực tiếp với IL và do đó có thể giả định các phương thức không ảo. Cả hai đều là sản phẩm thương mại.


59
+1 trên thực tế là bạn chỉ nên giả định giao diện. Câu hỏi này đã giải quyết những gì tôi đang chạy vào, bởi vì tôi vô tình chế nhạo lớp học và không phải giao diện cơ bản.
Paul Raff

1
Điều này không chỉ giải quyết vấn đề mà còn sử dụng các giao diện cho tất cả các lớp bạn cần kiểm tra. Moq về cơ bản là buộc bạn phải có Nghịch đảo phụ thuộc tốt trong đó một số khuôn khổ chế giễu khác cho phép bạn vượt qua nguyên tắc này.
Xipooo

Nó có bị coi là vi phạm nguyên tắc này không nếu tôi có triển khai giả mạo, ví dụ: FakeP PeopleRep repository, trên giao diện của tôi, ví dụ: IP PeopleRep repository và tôi đang chế giễu việc triển khai giả mạo? Tôi nghĩ rằng IoC vẫn được bảo tồn bởi vì trong thiết lập thử nghiệm của tôi, tôi phải chuyển đối tượng giả mạo đến lớp dịch vụ của mình, giao diện trong hàm tạo của nó.
paz

1
@paz Toàn bộ quan điểm của việc sử dụng Moq là để tránh việc thực hiện giả mạo. Bây giờ hãy xem xét có bao nhiêu biến thể của việc triển khai giả mà bạn sẽ cần kiểm tra các điều kiện biên, v.v. Về lý thuyết, vâng, bạn có thể chế nhạo việc thực hiện giả. Nhưng thực tế nó nghe giống như mùi mã.
Amol

Lưu ý rằng lỗi này thực sự có thể xảy ra với các phương thức mở rộng trên các giao diện, có thể gây nhầm lẫn.
Dan Pantry

34

Để giúp đỡ bất kỳ ai gặp vấn đề tương tự như tôi, tôi vô tình nhầm kiểu triển khai thay vì giao diện, vd

var mockFileBrowser = new Mock<FileBrowser>();

thay vì

var mockFileBrowser = new Mock<IFileBrowser>();

5

Vui lòng xem Tại sao tài sản tôi muốn giả cần phải là ảo?

Bạn có thể phải viết giao diện trình bao bọc hoặc đánh dấu thuộc tính là ảo / trừu tượng vì Moq tạo một lớp proxy mà nó sử dụng để chặn các cuộc gọi và trả về các giá trị tùy chỉnh mà bạn đã thực hiện trong .Returns(x)cuộc gọi.


5

Thay vì chế nhạo lớp bê tông, bạn nên chế giễu giao diện lớp đó. Trích xuất giao diện từ lớp XmlCupboardAccess

public interface IXmlCupboardAccess
{
    bool IsDataEntityInXmlCupboard(string dataId, out string nameInCupboard, out string refTypeInCupboard, string nameTemplate = null);
}

Và thay vì

private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();

thay đổi thành

private Mock<IXmlCupboardAccess> _xmlCupboardAccess = new Mock<IXmlCupboardAccess>();

3

Bạn cũng sẽ gặp lỗi này nếu bạn đang xác minh rằng phương thức mở rộng của giao diện được gọi.

Ví dụ: nếu bạn đang chế giễu:

var mockValidator = new Mock<IValidator<Foo>>();
mockValidator
  .Verify(validator => validator.ValidateAndThrow(foo, null));

Bạn sẽ nhận được ngoại lệ tương tự vì .ValidateAndThrow()là một phần mở rộng trên IValidator<T>giao diện.

public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance, string ruleSet = null)...


-12

Mã số:

private static void RegisterServices(IKernel kernel)
{
    Mock<IProductRepository> mock=new Mock<IProductRepository>();
    mock.Setup(x => x.Products).Returns(new List<Product>
    {
        new Product {Name = "Football", Price = 23},
        new Product {Name = "Surf board", Price = 179},
        new Product {Name = "Running shose", Price = 95}
    });

    kernel.Bind<IProductRepository>().ToConstant(mock.Object);
}        

nhưng thấy ngoại lệ.


4
Bạn có thể cung cấp một lời giải thích về giải pháp của bạn? Ngoài ra, "xem ngoại lệ ..." bị treo. Bạn có thể mở rộng về điều này?
sáng
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.