Moq를 사용하여 처음과 두 번째에 다른 반환 값


262

나는 다음과 같은 테스트를 받았다.

    [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두 번째 를 반환 하도록 할 수 있습니까?

답변:


452

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

연결 호출은 세 번째와 다섯 번째 시도에서만 성공하며 그렇지 않으면 예외가 발생합니다.

예를 들어 다음과 같습니다.

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

2
정답은 "SetupSequence"가 보호 된 멤버와 작동하지 않는다는 것입니다.
Chasefornone

7
아아, SetupSequence()와 함께 작동하지 않습니다 Callback(). 그렇게한다면, "상태 머신"방식으로 조롱 된 메소드에 대한 호출을 확인할 수 있습니다.
urig

@stackunderflow SetupSequence는 두 번의 호출에만 작동하지만 두 번 이상의 호출이 필요한 경우 어떻게해야합니까?
TanvirArjel

@TanvirArjel, 무슨 뜻인지 확실하지 않습니다 ... SetupSequence임의의 통화에 사용할 수 있습니다. 내가 준 첫 번째 예제는 5 번의 호출 시퀀스를 반환합니다.
stackunderflow

@stackunderflow 죄송합니다! 이것은 나의 오해였습니다! 예! 예상대로 작동합니다!
TanvirArjel

115

기존 답변은 훌륭하지만, 내가 그것을 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);

감사. pageModel.Object 대신 pageModel mock을 대기열에 넣는 오타를 수정 했으므로 이제는 빌드해야합니다! :)
mo.

3
대답은 정확하지만 Exception할 수없는 것처럼 던지기를 원하면 작동 하지 않습니다 Enqueue. 그러나 SetupSequence작동합니다 (예 : @stackunderflow의 답변 참조).
Halvard

4
Dequeue에 위임 된 메소드를 사용해야합니다. 샘플 작성 방법은 설정시 대기열에서 평가가 이루어 지므로 항상 대기열의 첫 번째 항목을 반복해서 반환합니다.
Jason Coyne

7
대의원입니다. 코드 Dequeue()가 just 대신에 포함되어 있으면 Dequeue올바른 것입니다.
mo.

31

콜백을 추가해도 효과가 없었 습니다. 대신 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);
    }

29

모의 객체를 설정할 때 콜백을 사용할 수 있습니다. Moq Wiki ( http://code.google.com/p/moq/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();
            });

1
이렇게하면 두 번 모두 null이 발생합니다. var pageModel = new Mock <IPageModel> (); IPageModel 모델 = null; repository.Setup (x => x.GetPageByUrl <IPageModel> (path)). Returns (() => model) .Callback (() => {model = pageModel.Object;});
marcus September

resolver.ResolvePath 메서드 내에서 GetPageByUrl이 두 번 호출됩니까?
Dan

ResolvePath에는 아래 코드가 포함되어 있지만 여전히 두 번 null입니다. var foo = _repository.GetPageByUrl <IPageModel> (virtualUrl); var foo2 = _repository.GetPageByUrl <IPageModel> (virtualUrl);
marcus September

2
콜백 접근 방식이 작동하지 않음을 확인했습니다 (이전 Moq 버전에서도 시도 됨). 테스트에 따라 가능한 또 다른 방법은 Setup()다시 전화를 걸고 Return()다른 값을하는 것입니다.
Kent Boogaart 2013


4

약간 다른 요구 사항으로 같은 종류의 문제에 대해 여기에 도달했습니다. 다른 입력 값을 기반으로 모의에서 다른 반환 값
을 가져 와야하며 Moq의 선언적 구문 (linq to Mocks)을 사용하기 때문에 IMO가 더 읽기 쉬운 솔루션을 찾았습니다.

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

나를 위해 (여기에서 2019 년 Moq 4.13.0), 더 짧고 람다가 전혀 필요 da.GetFromDb(0) == new Account { ..None.. && da.GetFromDb(1) == new Account { InActive } && ...하지 않은 경우에도 작동했습니다 It.Is.
ojdo

3

허용 대답 뿐만 아니라 SetupSequence의 대답은 , 핸들이 상수를 반환.

Returns()mocked 메소드로 전송 된 매개 변수를 기반으로 값을 리턴 할 수있는 유용한 과부하가 있습니다. 허용 된 답변에 제공된 솔루션을 기반으로 해당 과부하에 대한 또 다른 확장 방법이 있습니다.

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

여러 매개 변수 (로 확장 메서드에 대한 오버로드 만들기 T2, T3등) 필요한 경우.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.