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

我有一个这样的测试:

    [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第一次返回null,第二次返回pageModel.Object ?

135510 次浏览

您可以在设置模拟对象时使用回调。看一下来自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());

你的设置可能是这样的:

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

添加回调对我不起作用,我使用这种方法代替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);
}

现在可以使用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

现有的答案很好,但我想我应该抛出我的替代方案,它只使用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);

使用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);

同样的问题,但要求略有不同 我需要得到基于不同输入值的不同mock返回值和找到的解决方案,IMO更可读,因为它使用Moq的声明性语法(linq to Mocks)。< / p >

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

接受的答案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等)的重载。

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

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

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

在我的情况下,我有一个webapi调用,我需要mock;早些时候,它基于输入参数很好地工作,然而有一天,这些参数被转换为请求头。因此,由于我不能提供回调(没有函数参数),所以提出了另一种方法如下

[早些时候,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 });
});

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