Different return values the first and second time with Moq

后端 未结 7 620
感动是毒
感动是毒 2021-01-29 18:51

I have a test like this:

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


        
相关标签:
7条回答
  • 2021-01-29 18:55

    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);
    
    0 讨论(0)
  • 2021-01-29 19:07

    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);
        }
    
    0 讨论(0)
  • 2021-01-29 19:08

    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
    
    0 讨论(0)
  • 2021-01-29 19:09

    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.

    0 讨论(0)
  • 2021-01-29 19:10

    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);
    
    0 讨论(0)
  • 2021-01-29 19:10

    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
    
    0 讨论(0)
提交回复
热议问题