第一次和第二次 Moq 的返回值不同

发布于 2024-12-02 14:28:25 字数 1294 浏览 0 评论 0原文

我有一个这样的测试:

    [TestCase("~/page/myaction")]
    public void Page_With_Custom_Action(string path) {
        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);

        repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(path);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

GetPageByUrl 在我的 DashboardPathResolver 中运行两次,我如何告诉 Moq 第一次返回 nullpageModel.Object 第二个?

I have a test like this:

    [TestCase("~/page/myaction")]
    public void Page_With_Custom_Action(string path) {
        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);

        repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(path);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

GetPageByUrl runs twice in my DashboardPathResolver, how can I tell Moq to return null the first time and pageModel.Object the second?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

晚风撩人 2024-12-09 14:28:25

使用最新版本的 Moq(4.2.1312.1622),您可以使用 SetupSequence 设置事件序列。下面是一个示例:

_mockClient.SetupSequence(m => m.Connect(It.IsAny<String>(), It.IsAny<int>(), It.IsAny<int>()))
        .Throws(new SocketException())
        .Throws(new SocketException())
        .Returns(true)
        .Throws(new SocketException())
        .Returns(true);

调用 connect 仅在第三次和第五次尝试时才会成功,否则将引发异常。

所以对于你的例子来说它就像这样:

repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);

With the latest version of Moq(4.2.1312.1622), you can setup a sequence of events using SetupSequence. Here's an example:

_mockClient.SetupSequence(m => m.Connect(It.IsAny<String>(), It.IsAny<int>(), It.IsAny<int>()))
        .Throws(new SocketException())
        .Throws(new SocketException())
        .Returns(true)
        .Throws(new SocketException())
        .Returns(true);

Calling connect will only be successful on the third and fifth attempt otherwise an exception will be thrown.

So for your example it would just be something like:

repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);
天生の放荡 2024-12-09 14:28:25

现有的答案很好,但我想我应该提出我的替代方案,它只使用 System.Collections.Generic.Queue ,并且不需要任何模拟框架的特殊知识 - 因为我没有'我写的时候没有! :)

var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);

然后...

repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);

The existing answers are great, but I thought I'd throw in my alternative which just uses System.Collections.Generic.Queue and doesn't require any special knowledge of the mocking framework - since I didn't have any when I wrote it! :)

var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);

Then...

repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);
未蓝澄海的烟 2024-12-09 14:28:25

现在您可以使用SetupSequence。请参阅这篇文章

var mock = new Mock<IFoo>();
mock.SetupSequence(f => f.GetCount())
    .Returns(3)  // will be returned on 1st invocation
    .Returns(2)  // will be returned on 2nd invocation
    .Returns(1)  // will be returned on 3rd invocation
    .Returns(0)  // will be returned on 4th invocation
    .Throws(new InvalidOperationException());  // will be thrown on 5th invocation

Now you can use SetupSequence. See this post.

var mock = new Mock<IFoo>();
mock.SetupSequence(f => f.GetCount())
    .Returns(3)  // will be returned on 1st invocation
    .Returns(2)  // will be returned on 2nd invocation
    .Returns(1)  // will be returned on 3rd invocation
    .Returns(0)  // will be returned on 4th invocation
    .Throws(new InvalidOperationException());  // will be thrown on 5th invocation
断爱 2024-12-09 14:28:25

您可以在设置模拟对象时使用回调。查看 Moq Wiki 中的示例 (https://github.com/Moq/moq4/维基/快速入门)。

// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
    .Returns(() => calls)
    .Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());

您的设置可能如下所示:

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });

You can use a callback when setting up your mock object. Take a look at the example from the Moq Wiki (https://github.com/Moq/moq4/wiki/Quickstart).

// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
    .Returns(() => calls)
    .Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());

Your setup might look like this:

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });
久随 2024-12-09 14:28:25

添加回调对我来说不起作用,我使用了这种方法 http:// /haacked.com/archive/2009/09/29/moq-sequences.aspx 我最终得到了这样的测试:

    [TestCase("~/page/myaction")]
    [TestCase("~/page/myaction/")]
    public void Page_With_Custom_Action(string virtualUrl) {

        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
        repository.Setup(x => x.GetPageByUrl<IPageModel>(virtualUrl)).ReturnsInOrder(null, pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(virtualUrl);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

Adding a callback did not work for me, I used this approach instead http://haacked.com/archive/2009/09/29/moq-sequences.aspx and I ended up with a test like this:

    [TestCase("~/page/myaction")]
    [TestCase("~/page/myaction/")]
    public void Page_With_Custom_Action(string virtualUrl) {

        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
        repository.Setup(x => x.GetPageByUrl<IPageModel>(virtualUrl)).ReturnsInOrder(null, pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(virtualUrl);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }
巷子口的你 2024-12-09 14:28:25

接受的答案,以及SetupSequence 答案,处理返回常量。

Returns() 有一些有用的重载,您可以根据发送到模拟方法的参数返回一个值。根据已接受答案中给出的解决方案,这是另一个这些重载的扩展方法。

public static class MoqExtensions
{
    public static IReturnsResult<TMock> ReturnsInOrder<TMock, TResult, T1>(this ISetup<TMock, TResult> setup, params Func<T1, TResult>[] valueFunctions)
        where TMock : class
    {
        var queue = new Queue<Func<T1, TResult>>(valueFunctions);
        return setup.Returns<T1>(arg => queue.Dequeue()(arg));
    }
}

不幸的是,使用该方法需要指定一些模板参数,但结果仍然具有很好的可读性。

repository
    .Setup(x => x.GetPageByUrl<IPageModel>(path))
    .ReturnsInOrder(new Func<string, IPageModel>[]
        {
            p => null, // Here, the return value can depend on the path parameter
            p => pageModel.Object,
        });

如果需要,使用多个参数(T2T3 等)为扩展方法创建重载。

The accepted answer, as well as the SetupSequence answer, handles returning constants.

Returns() has some useful overloads where you can return a value based on the parameters that were sent to the mocked method. Based on the solution given in the accepted answer, here is another extension method for those overloads.

public static class MoqExtensions
{
    public static IReturnsResult<TMock> ReturnsInOrder<TMock, TResult, T1>(this ISetup<TMock, TResult> setup, params Func<T1, TResult>[] valueFunctions)
        where TMock : class
    {
        var queue = new Queue<Func<T1, TResult>>(valueFunctions);
        return setup.Returns<T1>(arg => queue.Dequeue()(arg));
    }
}

Unfortunately, using the method requires you to specify some template parameters, but the result is still quite readable.

repository
    .Setup(x => x.GetPageByUrl<IPageModel>(path))
    .ReturnsInOrder(new Func<string, IPageModel>[]
        {
            p => null, // Here, the return value can depend on the path parameter
            p => pageModel.Object,
        });

Create overloads for the extension method with multiple parameters (T2, T3, etc) if needed.

柏林苍穹下 2024-12-09 14:28:25

到达这里是为了解决同一类型的问题,但要求略有不同。
我需要从基于不同输入值的模拟中获取不同的返回值,并找到了 IMO 更具可读性的解决方案,因为它使用 Moq 的声明性语法(linq to Mocks)。

public interface IDataAccess
{
   DbValue GetFromDb(int accountId);  
}

var dataAccessMock = Mock.Of<IDataAccess>
(da => da.GetFromDb(It.Is<int>(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 2)) == new DbValue { AccountStatus = AccountStatus.Deleted });

var result1 = dataAccessMock.GetFromDb(0); // returns DbValue of "None" AccountStatus
var result2 = dataAccessMock.GetFromDb(1); // returns DbValue of "InActive"   AccountStatus
var result3 = dataAccessMock.GetFromDb(2); // returns DbValue of "Deleted" AccountStatus

Reached here for the same kind of problem with slightly different requirement.
I need to get different return values from mock based in different input values and found solution which IMO more readable as it uses Moq's declarative syntax (linq to Mocks).

public interface IDataAccess
{
   DbValue GetFromDb(int accountId);  
}

var dataAccessMock = Mock.Of<IDataAccess>
(da => da.GetFromDb(It.Is<int>(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 2)) == new DbValue { AccountStatus = AccountStatus.Deleted });

var result1 = dataAccessMock.GetFromDb(0); // returns DbValue of "None" AccountStatus
var result2 = dataAccessMock.GetFromDb(1); // returns DbValue of "InActive"   AccountStatus
var result3 = dataAccessMock.GetFromDb(2); // returns DbValue of "Deleted" AccountStatus
巷子口的你 2024-12-09 14:28:25

我们可以简单地声明一个使用 int 作为数据类型的变量。将其初始化为,然后按如下方式增加其值:

int firstTime = 0;
            repository.Setup(_ => _.GetPageByUrl<IPageModel>(path)).Returns(() =>
            {
                if (firstTime == 0)
                {
                    firstTime = 1;
                    return null;
                }
                else if(firstTime == 1)
                {
                    firstTime = 2;
                    return pageModel.Object;
                }
                else
                {
                    return null;
                }
            });

We can simply declare one variable with int as a datatype. initialize it to zero, and then increase it's value as follows:

int firstTime = 0;
            repository.Setup(_ => _.GetPageByUrl<IPageModel>(path)).Returns(() =>
            {
                if (firstTime == 0)
                {
                    firstTime = 1;
                    return null;
                }
                else if(firstTime == 1)
                {
                    firstTime = 2;
                    return pageModel.Object;
                }
                else
                {
                    return null;
                }
            });
最近可好 2024-12-09 14:28:25

在某些情况下,需要让被调用的函数根据您无法通过函数本身施加的条件返回不同类型的数据。如果函数接受参数,那么这些参数可以用作条件来获取不同的数据。

就我而言,我有一个需要模拟的 webapi 调用;早些时候,它根据输入参数运行得很好,但是有一天,这些参数被转换为请求标头。因此,由于我无法提供回调(没有函数参数),所以想出了另一种方法,如下

[以前的方法,当 API 有参数时]

this.mockedMasterAPICalls.Setup(m => m.GetCountries(It.Is<int>(ou => ou == 2), It.Is<int>(lan => lan == 1))).Returns(Task.FromResult(countryResponse));

[新的方法,当 API 有标头时。 .. 标头被注入到 API 调用者的另一个字典中]

   this.mockedMasterAPICalls.Setup(m => m.RequestHeaders).Returns(new Dictionary<string, string>());
            this.mockedMasterAPICalls.Setup(m => m.GetCountries()).Returns(() =>
          {
              if (this.mockedMasterAPICalls.Object.RequestHeaders[GlobalConstants.HeaderOUInstance] == "2")
                  return Task.FromResult(countryResponse);
              else return Task.FromResult(new GetCountryResponse() { Countries = null });
          });

请注意使用模拟对象本身来做出所需的任何决策

In some cases one needs to have the called function return different types of data based on conditions you cannot impose via the function itself. If the function accepts parameters then those can be used as conditionals to get different data.

In my case, I had a webapi call which I need to mock; earlier it was working out fine based on the input parameter, however one fine day, those parameters were converted to request headers. So since I couldn't provide a callback (no function parameters) so came up with another approach as follows

[Earlier one, when API had parameters]

this.mockedMasterAPICalls.Setup(m => m.GetCountries(It.Is<int>(ou => ou == 2), It.Is<int>(lan => lan == 1))).Returns(Task.FromResult(countryResponse));

[New one, when API had headers... The headers were getting injected into another dictionary of the API caller]

   this.mockedMasterAPICalls.Setup(m => m.RequestHeaders).Returns(new Dictionary<string, string>());
            this.mockedMasterAPICalls.Setup(m => m.GetCountries()).Returns(() =>
          {
              if (this.mockedMasterAPICalls.Object.RequestHeaders[GlobalConstants.HeaderOUInstance] == "2")
                  return Task.FromResult(countryResponse);
              else return Task.FromResult(new GetCountryResponse() { Countries = null });
          });

Note the use of the mocked object itself to make any decisions required

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文