【问题标题】:Moq: Invalid setup on a non-overridable member: x => x.GetByTitle("asdf")最小起订量:不可覆盖成员上的无效设置:x => x.GetByTitle("asdf")
【发布时间】:2010-12-30 00:57:39
【问题描述】:

不确定如何解决此问题,正在尝试对“GetByTitle”方法进行单元测试

这是我的定义:

public class ArticleDAO :  GenericNHibernateDAO(IArticle, int>, IArticleDAO
{
    public IArticle GetByTitle(string title)
    {
        IQuery query = Session.CreateQuery("...")
        return query.UniqueResult<IArticle>();
    }
}

public interface IArticleDAO
{
    IArticle GetByTitle(string title);
}

单元测试:

[Test]
public void can_load_by_title()
{
    _mockDaoFactory.Setup(x => x.GetArticleDao())
                                .Returns(_mockArticleDao.Object);
    _mockArticleDao.Setup(x => x.GetByTitle("some title"))
                                .Returns(article1.Object);

    _articleManager.LoadArticle("some title");

    Assert.IsNotNull(_articleManager.Article);
}

运行测试给了我错误:

System.ArgumentException: Invalid setup on a non-overridable member:
x => x.GetByTitle("some title")

更新

我的[Setup] 看起来像:

[Setup]
public void SetUp()
{
     _mockDaoFactory = new Mock<IDaoFactory>();
     _mockArticleDao = new Mock<ArticleDao>();

     _articleManager = new ArticleManager(_mockDaoFactory.Object);    
}

【问题讨论】:

  • 你在某处实例化_mockDaoFactory_mockArticleDao 吗?你是模拟类还是界面
  • 是的,我使用Interface在[Setup]中模拟了daofactory和mockarticleDao。 DAO 是使用类完成的。
  • @tomas 我用设置代码更新了我的问题。
  • 正如您在我的回答中看到的那样,您需要模拟界面(这是我推荐的)或标记GetByTitle 方法virtual
  • 您的测试中的第一行似乎也可以移至设置例程...?

标签: c# nhibernate nunit resharper moq


【解决方案1】:

为了控制模拟对象的行为(至少在 Moq 中),您要么需要模拟接口,要么确保您尝试控制的行为被标记为虚拟。在您的评论中,我理解_mockArticleDao 的实例化是这样的:

_mockArticleDao = new Mock<ArticleDAO>();

如果你想保持原样,你需要标记GetArticle方法virtual

public class ArticleDAO :  GenericNHibernateDAO(IArticle, int>, IArticleDAO
{
    public virtual IArticle GetByTitle(string title)
    {
        // ...
    }
}

否则(这是我推荐的),改为模拟界面。

_mockArticleDao = new Mock<IArticleDAO>();

【讨论】:

  • 但是由于 ArticleDAO 继承自 Generic.... ,如果我模拟接口 GenericNhibern 中的方法。将不可用?
  • 因为从工厂调用 GetArticleDAO 返回的是 ArticleDAO 而不是 IArticleDAO,所以 b/c articleDAO 还绑定到一个包含 nhibernate 内容的抽象类。
  • 如果你不能模拟接口,那么你可能正在测试错误的东西......但是,将方法标记为虚拟将解决问题。
  • +1 Tomas,我需要将参数注入 ctor,因此在我的情况下,我必须模拟实际类并将方法设置为虚拟,因为您不能将参数注入接口的医生。这是正确的方法吗?
  • @Kave:如果你需要在构造函数中注入一些东西,那你肯定是在测试错误的东西。模拟你给构造函数的任何东西,设置它的行为并测试 this 类的行为方式。如果需要,请编写一个新接口,使“注入”类型实现以访问所有方法签名。
【解决方案2】:

创建一个继承的可模拟类

我在尝试从框架中模拟一个我无法控制的类时遇到了同样的问题。在我的具体情况下,我必须模拟一个 HttpResponseMessage 设置状态代码以返回 Ok,但是如果该属性不是虚拟的,该怎么办?

此代码不起作用,因为 StatusCode 不是虚拟的:

var httpResponseMessage = new Mock<HttpResponseMessage>();
httpResponseMessage.SetupGet(x => x.StatusCode).Returns(HttpStatusCode.OK);

回答

  1. 在您的测试项目中创建一个新类,继承您要模拟的类
  2. 重新定义调用基本构造函数的同一组构造函数
  3. 重新定义要设置为虚拟的非虚拟属性或方法(使用 new 关键字显式隐藏原始成员)
  4. 从重新定义的虚拟属性或方法中,调用非虚拟基础属性或方法。

完成。现在您可以模拟一个派生对象,该派生对象可以在使用原始对象的任何地方使用,因为它继承自它。这是我的 MockableHttpResponseMessage 类的代码:

public class MockableHttpResponseMessage: HttpResponseMessage
{
    public MockableHttpResponseMessage() : base() {}
    public MockableHttpResponseMessage(HttpStatusCode code) : base (code) { }
    public new virtual HttpStatusCode StatusCode { 
        get { return base.StatusCode; } 
        set { base.StatusCode = value; }
    }        
}

现在,这段代码可以工作了:

var httpResponseMessage = new Mock<MockableHttpResponseMessage>();
httpResponseMessage.SetupGet(x => x.StatusCode).Returns(HttpStatusCode.OK);

【讨论】:

    【解决方案3】:

    这是我模拟 HttpMessageHandler 的方式:

    private HttpRequestMessage requestMessage = new HttpRequestMessage();
    Mock<HttpMessageHandler> handlerMock = 
    GetHttpMessageHandlerMock(HttpStatusCode.OK);  
    
    MyRestService myRestService = new MyRestService();
    myRestService.client = new HttpClient(handlerMock.Object);
    
    var response = myRestService.Get("");
    

    //此时调用HttpRequestMessage的Mock,Callback填充我的类变量requestMessage。我现在可以查看 requestMessage 内部了。

    var headers = requestMessage?.Headers.ToString();
    var queryBegin = requestMessage.RequestUri.OriginalString.IndexOf('?');
    var queryString = requestMessage.RequestUri.OriginalString.Substring(queryBegin + 1);
            Assert.That(headers.Contains("x-api-key: fakeApiKey"));
    

    //下面的辅助方法

    private Mock<HttpMessageHandler> GetHttpMessageHandlerMock(HttpStatusCode statusCode)
    {
            var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
            handlerMock
               .Protected()
               .Setup<Task<HttpResponseMessage>>(
                  "SendAsync",
                  ItExpr.IsAny<HttpRequestMessage>()
                 , ItExpr.IsAny<CancellationToken>()
               )
               .Returns(Task.FromResult(GetFakeResponse(statusCode)))
               .Callback<HttpRequestMessage, CancellationToken>((p, q) => requestMessage = p)
              .Verifiable();
            return handlerMock;
        }
    
    
        private HttpResponseMessage GetFakeResponse(HttpStatusCode statusCode)
        {
            var s = "{\"data\":{\"status\":\"SUCCESS\",\"errorCode\":\"\",\"errorMessage\":\"9\"}}";
            HttpResponseMessage response = new HttpResponseMessage()
            {
    
                StatusCode = statusCode,
                Content = new StringContent(s),
                ReasonPhrase = "OK",
                RequestMessage = new HttpRequestMessage()
            };
            return response;
        }
    

    我几乎所有的 REST 测试都使用它,因为我可以传入状态、内容等。因此,我可以测试不同的返回值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-19
      • 2015-09-05
      • 2012-08-09
      • 1970-01-01
      • 1970-01-01
      • 2012-07-03
      • 1970-01-01
      相关资源
      最近更新 更多