Làm cách nào để sử dụng Moq để mô phỏng một phương thức mở rộng?


87

Tôi đang viết một bài kiểm tra phụ thuộc vào kết quả của một phương thức mở rộng nhưng tôi không muốn phương pháp mở rộng đó sẽ không bao giờ phá vỡ bài kiểm tra này trong tương lai. Chế giễu kết quả đó dường như là lựa chọn hiển nhiên nhưng Moq dường như không đưa ra cách ghi đè phương thức tĩnh (một yêu cầu đối với phương thức mở rộng). Có một ý tưởng tương tự với Moq.Protected và Moq.Stub, nhưng họ dường như không cung cấp bất cứ điều gì cho tình huống này. Tôi đang thiếu một cái gì đó hay tôi nên đi về điều này theo một cách khác?

Đây là một ví dụ nhỏ không thành công với thông thường "Kỳ vọng không hợp lệ trên thành viên không thể ghi đè" . Đây là một ví dụ tồi về việc bắt chước một phương thức mở rộng, nhưng nó nên làm.

public class SomeType {
    int Id { get; set; }
}

var ListMock = new Mock<List<SomeType>>();
ListMock.Expect(l => l.FirstOrDefault(st => st.Id == 5))
        .Returns(new SomeType { Id = 5 });

Đối với bất kỳ người nghiện TypeMock nào có thể đề nghị tôi sử dụng Isolator thay thế: Tôi đánh giá cao nỗ lực vì có vẻ như TypeMock có thể thực hiện công việc bị bịt mắt và say xỉn, nhưng ngân sách của chúng tôi sẽ không sớm tăng lên.


3
Có thể tìm thấy bản sao tại đây: stackoverflow.com/questions/2295960/… .
Oliver

6
Câu hỏi này cũ hơn câu hỏi đó một năm. Nếu có sự trùng lặp, nó sẽ đi theo hướng khác.
patridge

2
Vẫn chưa có giải pháp thích hợp trong năm 2019!
TanvirArjel

1
@TanvirArjel Trên thực tế, từ nhiều năm, bạn có thể sử dụng JustMock để mô phỏng các phương thức mở rộng. Và cũng đơn giản như chế nhạo bất kỳ phương pháp nào khác. Đây là một liên kết đến tài liệu:
Chế độ

Câu trả lời:


69

Các phương thức mở rộng chỉ là các phương thức tĩnh trong ngụy trang. Các khung công tác chế nhạo như Moq hoặc Rhinomocks chỉ có thể tạo các phiên bản giả của các đối tượng, điều này có nghĩa là không thể chế tạo các phương thức tĩnh.


68
@ Mendelt..Vậy thì làm thế nào để một đơn vị kiểm tra một phương thức mà bên trong có một phương thức mở rộng? các lựa chọn thay thế có thể là gì?
Sai Avinash

@Mendelt, làm cách nào để một đơn vị kiểm tra một phương pháp mà bên trong có một phương thức mở rộng? các lựa chọn thay thế có thể là gì?
Alexander

@Alexander Tôi cũng có câu hỏi tương tự và câu này đã trả lời nó một cách tuyệt vời: agooddayforscience.blogspot.com/2017/08/… -Hãy xem này, nó là một cứu cánh!
LTV

30

Nếu bạn có thể thay đổi mã phương thức tiện ích mở rộng thì bạn có thể viết mã như thế này để có thể kiểm tra:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

public static class MyExtensions
{
    public static IMyImplementation Implementation = new MyImplementation();

    public static string MyMethod(this object obj)
    {
        return Implementation.MyMethod(obj);
    }
}

public interface IMyImplementation
{
    string MyMethod(object obj);
}

public class MyImplementation : IMyImplementation
{
    public string MyMethod(object obj)
    {
        return "Hello World!";
    }
}

Vì vậy, các phương thức mở rộng chỉ là một trình bao bọc xung quanh giao diện thực thi.

(Bạn có thể chỉ sử dụng lớp triển khai mà không có các phương thức mở rộng là một loại đường cú pháp.)

Và bạn có thể giả lập giao diện triển khai và đặt nó làm giao diện triển khai cho lớp phần mở rộng.

public class MyClassUsingExtensions
{
    public string ReturnStringForObject(object obj)
    {
        return obj.MyMethod();
    }
}

[TestClass]
public class MyTests
{
    [TestMethod]
    public void MyTest()
    {
        // Given:
        //-------
        var mockMyImplementation = new Mock<IMyImplementation>();

        MyExtensions.Implementation = mockMyImplementation.Object;

        var myClassUsingExtensions = new MyClassUsingExtensions();

        // When:
        //-------
        var myObject = new Object();
        myClassUsingExtensions.ReturnStringForObject(myObject);

        //Then:
        //-------
        // This would fail because you cannot test for the extension method
        //mockMyImplementation.Verify(m => m.MyMethod());

        // This is success because you test for the mocked implementation interface
        mockMyImplementation.Verify(m => m.MyMethod(myObject));
    }
}

Cảm ơn bạn rất nhiều cho việc này. Nó cực kỳ hữu ích và giúp tôi vượt qua một số trở ngại trong thử nghiệm.
DerHaifisch

Đây là một bình luận bị đánh giá thấp.
Raj


15

Tôi đã tạo một lớp trình bao bọc cho các phương thức mở rộng mà tôi cần mô phỏng.

public static class MyExtensions
{
    public static string MyExtension<T>(this T obj)
    {
        return "Hello World!";
    }
}

public interface IExtensionMethodsWrapper
{
    string MyExtension<T>(T myObj);
}

public class ExtensionMethodsWrapper : IExtensionMethodsWrapper
{
    public string MyExtension<T>(T myObj)
    {
        return myObj.MyExtension();
    }
}

Sau đó, bạn có thể mô phỏng các phương thức trình bao bọc trong các thử nghiệm và mã bằng vùng chứa IOC của bạn.


Đây là một giải pháp mà bạn không thể sử dụng cú pháp các phương thức tiện ích mở rộng nữa trong mã của mình. Nhưng nó sẽ hữu ích khi bạn không thể thay đổi lớp phương thức mở rộng.
Informatorius

@informatorius Ý bạn là gì? Trong mã của bạn, bạn sử dụng MyExtension () từ lớp MyExtensions. Trong các thử nghiệm của mình, bạn sử dụng MyExtension () từ lớp ExtensionMethodsWrapper () để cung cấp tham số.
ranthonissen

Nếu lớp của tôi đang được kiểm tra sử dụng MyExtension () từ MyExtensions thì tôi không thể bắt chước MyExtensions. Vì vậy, lớp được kiểm tra phải sử dụng IExtensionMethodsWrapper để nó có thể được chế tạo. Nhưng sau đó lớp được kiểm tra không thể sử dụng cú pháp phương thức mở rộng nữa.
Informatorius

1
Đó là toàn bộ điểm của OP. Đây là một giải pháp cho điều đó.
ranthonissen

4

Đối với các phương pháp mở rộng, tôi thường sử dụng phương pháp sau:

public static class MyExtensions
{
    public static Func<int,int, int> _doSumm = (x, y) => x + y;

    public static int Summ(this int x, int y)
    {
        return _doSumm(x, y);
    }
}

Nó cho phép tiêm _doSumm khá dễ dàng.


2
Tôi vừa thử điều này nhưng có vấn đề khi truyền đối tượng bị chế nhạo Parent mà tiện ích mở rộng dành cho khi truyền nó vào một số phương thức khác nằm trong một hội đồng khác. Trong trường hợp đó, ngay cả với kỹ thuật này, phần mở rộng ban đầu vẫn được chọn khi phương thức được gọi trong assembly khác, đây là một vấn đề phạm vi. Mặc dù vậy, nếu tôi gọi trực tiếp trong dự án Unit Test thì không sao, nhưng khi bạn đang thử nghiệm mã khác gọi phương thức mở rộng thì điều đó không hữu ích.
Stephen York

@Stephen Không chắc chắn cách tránh điều này. Tôi không bao giờ có loại này của một vấn đề Phạm vi
dmigo

0

Điều tốt nhất bạn có thể làm là cung cấp triển khai tùy chỉnh cho loại có phương thức mở rộng, ví dụ:

[Fact]
public class Tests
{
    public void ShouldRunOk()
    {
        var service = new MyService(new FakeWebHostEnvironment());

        // Service.DoStuff() internally calls the SomeExtensionFunction() on IWebHostEnvironment
        // Here it works just fine as we provide a custom implementation of that interface
        service.DoStuff().Should().NotBeNull();
    }
}

public class FakeWebHostEnvironment : IWebHostEnvironment
{
    /* IWebHostEnvironment implementation */

    public bool SomeExtensionFunction()
    {
        return false;
    }
}
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.