Moq로 특정 매개 변수 확인


170
public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    var queueableMessage = CreateSingleQueueableMessage();
    var message = queueableMessage[0];
    var xml = QueueableMessageAsXml(queueableMessage);
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(xml)).Verifiable();
    //messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();

    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(essageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    //messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(xml), Times.Once());
    messageServiceClientMock.Verify();
}

나는 Moq를 사용하기 시작하고 조금 어려움을 겪고 있습니다. messageServiceClient가 올바른 매개 변수 (XmlElement)를 수신하고 있는지 확인하려고하지만 작동시킬 방법을 찾을 수 없습니다. 특정 값을 확인하지 않은 경우에만 작동합니다.

어떤 아이디어?

부분 답변 : 프록시로 전송 된 xml이 올바른지 테스트하는 방법을 찾았지만 여전히 올바른 방법이라고 생각하지 않습니다.

public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(messageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    var message = CreateMessage();
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(It.Is<XmlElement>(xmlElement => XMLDeserializer<QueueableMessage>.Deserialize(xmlElement).Messages.Contains(message))), Times.Once());
}

그런데 Verify 호출에서 식을 어떻게 추출 할 수 있습니까?

답변:


251

검증 로직이 사소한 것이 아니라면 (예에서 보듯이) 큰 람다 방법을 작성하는 것이 지저분합니다. 모든 테스트 문을 별도의 방법으로 넣을 수는 있지만 테스트 코드 읽기 흐름을 방해하기 때문에 이것을 좋아하지 않습니다.

또 다른 옵션은 Setup 호출에서 콜백을 사용하여 모의 메소드에 전달 된 값을 저장 한 다음 표준 Assert메소드를 작성 하여이를 검증하는 것입니다. 예를 들면 다음과 같습니다.

// Arrange
MyObject saveObject;
mock.Setup(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()))
        .Callback<int, MyObject>((i, obj) => saveObject = obj)
        .Returns("xyzzy");

// Act
// ...

// Assert
// Verify Method was called once only
mock.Verify(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()), Times.Once());
// Assert about saveObject
Assert.That(saveObject.TheProperty, Is.EqualTo(2));

6
이 방법의 한 가지 큰 이점은 개별적으로 테스트 할 때 개체가 어떻게 잘못되었는지에 대한 특정 테스트 실패를 제공한다는 것입니다.
Rob Church

1
나는 이것이 내가 한 유일한 사람이라고 생각했는데, 그것이 합리적인 접근 방식임을 알게되어 기쁘다!
윌 애플비

3
Mayo에 따라 It.Is <MyObject> (validator)를 사용하는 것이 람다의 일부로 매개 변수 값을 저장하는 약간 어색한 방법을 피하기 때문에 더 좋습니다
stevec

테스트를 병렬로 실행할 때이 스레드가 안전합니까?
Anton Tolken

@AntonTolken 나는 그것을 시도하지 않았지만, 내 예제에서 업데이트 된 객체는 로컬 변수 (saveObject)이므로 스레드 안전해야합니다.
Rich Tebb

113

같은 방식으로 전화를 확인하고 있습니다. 전화를하는 것이 올바른 방법이라고 생각합니다.

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => mo.Id == 5 && mo.description == "test")
  ), Times.Once());

람다식이 다루기 어려워지면 MyObject입력 및 출력으로 사용되는 함수를 만들 수 있습니다 true/ false...

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => MyObjectFunc(mo))
  ), Times.Once());

private bool MyObjectFunc(MyObject myObject)
{
  return myObject.Id == 5 && myObject.description == "test";
}

또한 메소드가 호출되지 않았을 때 메소드가 여러 번 호출되었다는 오류 메시지가 표시되는 Mock의 버그에 유의하십시오. 그들은 지금까지 그것을 고쳤을 것입니다. 그러나 당신이 그 메시지를 보게되면 메소드가 실제로 호출되었는지 확인하는 것을 고려할 수 있습니다.

편집 : 다음은 목록의 각 객체에 대해 함수를 호출하는지 확인하려는 시나리오에서 여러 번 확인을 호출하는 예입니다 (예 :).

foreach (var item in myList)
  mockRepository.Verify(mr => mr.Update(
    It.Is<MyObject>(i => i.Id == item.Id && i.LastUpdated == item.LastUpdated),
    Times.Once());

동일한 설정 방법 ...

foreach (var item in myList) {
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);
}

따라서 해당 itemId에 대해 GetStuff가 호출 될 때마다 해당 항목에 특정한 항목을 반환합니다. 또는 itemId를 입력으로 받아서 물건을 반환하는 함수를 사용할 수 있습니다.

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));

필자가 언젠가 블로그에서 보았던 다른 방법 (Phil Haack?)은 일종의 dequeue 객체에서 돌아 오는 설정을 가지고 있습니다. 함수가 호출 될 때마다 대기열에서 항목을 가져옵니다.


1
고마워요. 여전히 이해할 수없는 것은 설정 또는 확인에서 세부 정보를 지정할 때입니다. 꽤 혼란 스럽습니다. 현재 Setup에서 아무것도 허용하고 Verify에서 값을 지정하고 있습니다.
Luis Mirabal

전화가 여러 개인 경우 메시지를 확인할 수 있다고 생각하십니까? 클라이언트가 메시지를 가져 와서 여러 개의 대기 가능한 메시지를 만들 수 있습니다. 이는 여러 번의 호출로 끝나고 각 호출마다 다른 메시지를 확인해야합니다. 나는 여전히 단위 테스트에 어려움을 겪고 있지만 아직 익숙하지는 않습니다.
Luis Mirabal

나는 당신이 이것을 해야하는 방법에 마술은 총알이 없다고 생각합니다. 연습이 필요하고 나아지기 시작합니다. 나를 위해 매개 변수를 비교할 때와 다른 테스트에서 해당 매개 변수를 아직 테스트하지 않은 경우에만 매개 변수를 지정합니다. 다중 호출에 대해서는 몇 가지 접근 방식이 있습니다. 여러 번 호출되는 함수를 설정하고 확인하기 위해 보통 각 루프마다 setup 또는 verify (Times.Once ())를 호출합니다. 특정 매개 변수를 사용하여 각 호출을 분리 할 수 ​​있습니다.
Mayo

여러 통화에 대한 몇 가지 예를 추가했습니다 (위 답변 참조).
Mayo

1
"또한 오류 메시지가 호출되지 않았을 때 메소드가 여러 번 호출되었다는 오류 메시지가 표시되는 Mock의 버그를 알고 있어야합니다. 지금까지 수정되었을 수 있습니다. 그러나 해당 메시지가 표시되면 이 방법이 실제로 호출되었습니다. " -이와 같은 버그는 조롱 라이브러리 IMHO를 완전히 무효화합니다. 아이러니 그들은 :-) 그것을위한 적절한 테스트 코드를 가지고 있지 않았다
지안루카 Ghettini

20

더 간단한 방법은 다음과 같습니다.

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);

이 작업을 수행 할 수없는 것 같습니다 .Code.WRCC를 매개 변수로 사용하여 메서드를 호출했는지 확인하려고합니다. 하지만 내 테스트는 항상 전달 된 매개 변수가 WRDD 경우에도 통과 ... m.Setup(x => x.CreateSR(Code.WRDD)).ReturnsAsync(0); await ClassUnderTest.CompleteCC(accountKey, It.IsAny<int>(), mockRequest); m.Verify(x => x.CreateSR(It.Is<Code>(p => p == Code.WRCC)), Times.Once());
그렉 퀸

1

나는 Moq가 평등을 점검한다는 사실의 문제를 믿는다. 그리고 XmlElement는 Equals를 재정의하지 않기 때문에 구현시 참조 동등성을 검사합니다.

맞춤 객체를 사용할 수 없어서 등호를 재정의 할 수 있습니까?


그렇습니다. 문제가 Xml을 확인하고 있음을 깨달았습니다. 질문의 두 번째 부분에서 XML에 객체를 직렬화 해제하는 가능한 대답을 추가했습니다.
Luis Mirabal

1

이것들 중 하나도 있었지만 액션의 매개 변수는 공용 속성이없는 인터페이스였습니다. 별도의 메소드와 함께 It.Is ()를 사용하여 종료 되었으며이 메소드 내에서 인터페이스를 조롱해야했습니다.

public interface IQuery
{
    IQuery SetSomeFields(string info);
}

void DoSomeQuerying(Action<IQuery> queryThing);

mockedObject.Setup(m => m.DoSomeQuerying(It.Is<Action<IQuery>>(q => MyCheckingMethod(q)));

private bool MyCheckingMethod(Action<IQuery> queryAction)
{
    var mockQuery = new Mock<IQuery>();
    mockQuery.Setup(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition())
    queryAction.Invoke(mockQuery.Object);
    mockQuery.Verify(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition(), Times.Once)
    return true
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.