Diferentes valores de retorno la primera y segunda vez con Moq.

Tengo una prueba como esta:

[TestCase("~/page/myaction")] public void Page_With_Custom_Action(string path) { // Arrange var pathData = new Mock(); var pageModel = new Mock(); var repository = new Mock(); var mapper = new Mock(); var container = new Mock(); container.Setup(x => x.GetInstance()).Returns(repository.Object); repository.Setup(x => x.GetPageByUrl(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 se ejecuta dos veces en mi dashboardpathresolver, ¿cómo puedo decirle a Moq que devuelva null la primera vez y pageModel.Ojbect la segunda?

Con la última versión de Moq (4.2.1312.1622), puede configurar una secuencia de eventos usando SetupSequence . Aquí hay un ejemplo:

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

Llamar a conectar solo tendrá éxito en el tercer y quinto bash, de lo contrario se lanzará una excepción.

Entonces, para tu ejemplo, sería algo como:

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

Las respuestas existentes son geniales, pero pensé que iba a incluir mi alternativa que solo usa System.Collections.Generic.Queue y no requiere ningún conocimiento especial del marco de burla, ya que no tenía ninguno cuando lo escribí. ! 🙂

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

Entonces…

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

Agregar una callback no me funcionó, usé este enfoque en su lugar http://haacked.com/archive/2009/09/29/moq-sequences.aspx y terminé con una prueba como esta:

  [TestCase("~/page/myaction")] [TestCase("~/page/myaction/")] public void Page_With_Custom_Action(string virtualUrl) { // Arrange var pathData = new Mock(); var pageModel = new Mock(); var repository = new Mock(); var mapper = new Mock(); var container = new Mock(); container.Setup(x => x.GetInstance()).Returns(repository.Object); repository.Setup(x => x.GetPageByUrl(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); } 

Puede utilizar una callback al configurar su objeto simulado. Eche un vistazo al ejemplo de Moq Wiki ( http://code.google.com/p/moq/wiki/QuickStart ).

 // returning different values on each invocation var mock = new Mock(); 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()); 

Su configuración podría verse así:

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

Ahora puedes usar SetupSequence. Consulte esta publicación: http://codecontracts.info/2011/07/28/moq-setupsequence-is-great-for-mocking/

Llegue aquí para el mismo tipo de problema con requisitos ligeramente diferentes.
Necesito obtener diferentes valores de retorno de simulacros basados ​​en diferentes valores de entrada y una solución que IMO sea más legible ya que utiliza la syntax declarativa de Moq (linq to Mocks).

 public interface IDataAccess { DbValue GetFromDb(int accountId); } var dataAccessMock = Mock.Of (da => da.GetFromDb(It.Is(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None } && da.GetFromDb(It.Is(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive } && da.GetFromDb(It.Is(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 

La respuesta aceptada , así como la respuesta de SetupSequence , manejan las constantes de retorno.

Returns() tiene algunas sobrecargas útiles donde puede devolver un valor basado en los parámetros que se enviaron al método simulado. Basado en la solución dada en la respuesta aceptada, aquí hay otro método de extensión para esas sobrecargas.

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

Desafortunadamente, el uso del método requiere que especifique algunos parámetros de plantilla, pero el resultado todavía es bastante legible.

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

Cree sobrecargas para el método de extensión con múltiples parámetros ( T2 , T3 , etc.) si es necesario.