विभिन्न रिटर्न मू और पहली बार दूसरी बार मो


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" संरक्षित सदस्यों के साथ काम नहीं करता है।
चेसफॉर्नोन

7
काश, SetupSequence()साथ काम नहीं करता Callback()। यदि केवल यह किया जाता है, तो कोई "राज्य मशीन" फैशन में नकली विधि से कॉल को सत्यापित कर सकता है।
उरईग

@stackunderflow SetupSequenceकेवल दो कॉल के लिए काम करती है लेकिन दो कॉल से अधिक की आवश्यकता होने पर मैं क्या कर सकता हूं?
तनवीरअरजेल

@TanvirArjel, सुनिश्चित नहीं हैं कि आपका क्या मतलब है ... SetupSequenceका उपयोग मनमाने ढंग से कॉल के लिए किया जा सकता है। पहला उदाहरण मैंने रिटर्न का 5 कॉल का एक क्रम दिया।
स्टैकेन्डरफ्लो

@stackunderflow क्षमा करें! यह मेरी गलतफहमी थी! हाँ! आप उम्मीद के मुताबिक काम कर रहे हैं!
तनवीरअर्जेल

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

धन्यवाद। मैंने सिर्फ टाइपो को ठीक किया जहाँ मैं पेजमॉडल के बजाय पेजमॉडल का मजाक उड़ा रहा था। विशेषण, तो अब इसे भी बनाना चाहिए! :)
मो।

3
उत्तर सही है, लेकिन ध्यान दें कि यदि आप इसे Exceptionनहीं फेंक सकते हैं तो Enqueueयह काम नहीं करेगा। लेकिन SetupSequenceकाम करेगा (उदाहरण के लिए @stackunderflow से उत्तर देखें)।
हालवर्ड

4
आपको Dequeue के लिए एक प्रत्यायोजित विधि का उपयोग करना होगा। जिस तरह से नमूना लिखा गया है वह हमेशा कतार में पहली वस्तु को बार-बार लौटाएगा, क्योंकि सेटअप के समय डीक्यू का मूल्यांकन किया जाता है।
जेसन कॉइन

7
वह एक प्रतिनिधि है। यदि कोड Dequeue()केवल के बजाय समाहित है Dequeue, तो आप सही होंगे।
मो।

31

कॉलबैक जोड़ने से मेरे लिए काम नहीं किया गया, मैंने http://haacked.com/archive/2009/09/29/moq-fterences.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
मुझे ऐसा करने पर दोनों समय अशक्त हो जाते हैं: var pageModel = new Mock <IPageModel> (); IPageModel मॉडल = null; repository.Setup (x => x.GetPageByUrl <IPageModel> (path))। रिटर्न () => मॉडल) .Callback (() => {मॉडल = pageModel.Object;});
marcus

क्या GetPageByUrl को दो बार resolver.ResolvePath विधि के भीतर बुलाया जाता है?
दान

ResolvePath में नीचे दिया गया कोड है, लेकिन यह अभी भी दोनों बार var foo = _repository.GetPageByUrl <IPageModel> (virtualUrl) को शून्य करता है; var foo2 = _repository.GetPageByUrl <IPageModel> (virtualUrl);
मार्कस

2
पुष्टि की कि कॉलबैक दृष्टिकोण काम नहीं करता है (यहां तक ​​कि पहले के Moq संस्करण में भी प्रयास किया गया है)। एक अन्य संभावित दृष्टिकोण - आपके परीक्षण के आधार पर - बस Setup()फिर से कॉल करना है, और Return()एक अलग मूल्य है।
कैंट बूगार्ट


4

थोड़ी अलग आवश्यकता के साथ एक ही तरह की समस्या के लिए यहां तक ​​पहुंचे।
मुझे अलग-अलग इनपुट मूल्यों के आधार पर मॉक से अलग-अलग रिटर्न वैल्यू प्राप्त करने की जरूरत है और पाया गया समाधान जो IMO अधिक पठनीय है क्योंकि यह Moq के डिक्लेक्टिव सिंटैक्स (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

मेरे लिए (2019 से Moq 4.13.0 यहाँ), इसने छोटे da.GetFromDb(0) == new Account { ..None.. && da.GetFromDb(1) == new Account { InActive } && ..., नो- It.Isलैम्ब्डा के साथ भी काम किया ।
ओजडो

3

स्वीकार किए जाते हैं जवाब , साथ ही 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,
        });

एक से अधिक पैरामीटर (साथ विस्तार विधि के लिए भार के बनाएं T2, T3, आदि) यदि आवश्यक हो तो।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.