简体   繁体   English

如何使用列表存储模拟对象

[英]How to use a list to store Mock objects

I am a new guy in this Moq community. 我是这个Moq社区的新成员。

I am wondering is there any way to put an Inheritance object of A (Mock) to a list of Mock interface A? 我想知道有什么方法可以将A(模拟)的继承对象放到模拟接口A的列表中吗?

For example, assume we have 例如,假设我们有

Class ClientA :IMyClient{...}   //has override virtual codes
Class ClientB :IMyClient{...}

and

 List<Mock<IMyClient>> mockClients = new List<Mock<IMyClient>>();

Is there anyway that I can do something like this: 无论如何,我可以做这样的事情:

var mockClientA = new Mock<ClientA>();
mockClients.Add(mockClientA);

I might not be on the right track. 我可能走错了路。 Need some help. 需要一些帮助。 Thanks. 谢谢。

================================= ================================

Setup 设定

   // setup everyone to Free
    actualManager.Status = Status.Free;
    foreach (var mock in mockClients) {
        mock.Object.Status = Status.Free;
        actualManager.AddClient(mock.Object);
    }

Test 测试

    Assert.AreEqual(Status.Free, actualManager.Status); // pass
    var firstClient = mockClients.First();
    IMyClient actualFirstClient  = SetClientToTriggerOutsideCallbacks(firstClient);
    firstClient.Status = Status.Busy;

    // busy, since the first client is busy now.
    Assert.AreEqual(Status.Busy, actualManager.Status);  //pass
    SessionSwitchEventArgs e = new SessionSwitchEventArgs(SessionSwitchReason.SessionLock);
    actualManager.SystemEvents_SessionSwitch(null, e);
    Assert.AreEqual(Status.Away, actualManager.Status);   //not pass

I was trying to check influence of status changes on different clients, so I was trying to use a list to store all the clients. 我试图检查状态更改对不同客户端的影响,所以我试图使用列表来存储所有客户端。 The problem is: actualManager.SystemEvents_SessionSwitch(null, e); 问题是: actualManager.SystemEvents_SessionSwitch(null, e); doesn't work correctly if the first client is not in type of ClientA. 如果第一个客户端不是ClientA类型,则无法正常工作。

First of all - why do we need mocks and stubs ? 首先-为什么我们需要模拟和存根 Because our SUT (System Under Test, the object which you are testing) often has some dependencies. 因为我们的SUT(被测系统,即您要测试的对象)通常具有某些依赖性。 We cannot use real dependencies which we have in production code, because we will not be able to tell the reason why test is passing or failing. 我们不能使用生产代码中存在的实际依赖关系,因为我们无法说出测试通过或失败的原因。 It might be error in SUT or in dependency. SUT或依赖项中可能有错误。 So we should provide something instead of real dependency which will be reliable . 因此,我们应该提供一些可靠的替代实际依赖的方法。 Ie it should not produce any unexpected errors no matter how you change implementation of real dependencies. 即,无论您如何更改实际依赖项的实现,它都不会产生任何意外错误。 That's why we use stubs and mocks. 这就是为什么我们使用存根和模拟。

But what's the difference between them? 但是它们之间有什么区别? Stubs are used in tests which do state verification . 存根用于进行状态验证的测试中。 Ie when you after exercising SUT verify state of SUT and (sometime) stub. 即,当您执行SUT之后,请验证SUT和(有时)存根的状态。 Stub can be very simple. 存根可以很简单。 You can just implement dependency interface and use auto-properties without any logic inside. 您可以仅实现依赖项接口并使用自动属性,而无需内部任何逻辑。 It's enough for you to setup some value, SUT will read/write something into stub. 设置一些值就足够了,SUT会将一些内容读/写到存根中。 At the end you will check state. 最后,您将检查状态。 You will not check what operations were performed. 您将不会检查执行了哪些操作。

Behavior verification is very different. 行为验证是非常不同的。 It focuses on interaction between SUT and dependencies. 它着重于SUT与依赖项之间的交互。 We setup some expectations for mock object. 我们对模拟对象设置了一些期望。 It's not same as setting up some properties with fake data. 这与使用伪数据设置某些属性不同。 With mock you setup the fact that property or method should be called, and correct parameters should be passed. 通过模拟,您可以设置以下事实:应调用属性或方法,并应传递正确的参数。 With stub you don't check whether it was called. 使用存根,您无需检查它是否被调用。 You just make call possible and check state at the end. 您只需使呼叫成为可能,然后检查状态即可。

So.. what you are doing here is state verification with mocks. 所以..您在这里所做的是使用模拟进行状态验证 Which is not how mocks supposed to be used. 这不是应该使用模拟程序的方式。 Ie you setup status of clients and then check state of manager. 即您设置客户端状态,然后检查管理器状态。 Instead of using Moq following simple implementation completely satisfies your needs: 只需简单的实现即可完全满足您的需求,而无需使用Moq:

public class ClientStub : IMyClient
{
    public Status Status { get; set; }
    // some other members here
}

You can set status. 您可以设置状态。 Manager can read it. 管理员可以阅读。 You can check manager's state. 您可以检查经理的状态。 If you are going to use mocks, you should use behavior verification (otherwise it's just overkill). 如果要使用模拟,则应使用行为验证(否则就太过分了)。 It's hard to tell what is the purpose of your manager class and how it checks status of clients. 很难说出经理类的目的是什么,以及它如何检查客户的状态。 If you'll add this information I'll add some behavior verification test for your manager. 如果您将添加此信息,则将为您的经理添加一些行为验证测试​​。

And regarding your question about putting mocks of different types into list - you cannot do that (well, only if you have list of objects). 关于将不同类型的模拟物放到列表中的问题-您不能这样做(嗯,仅当您有对象列表时)。 And it makes no sense. 这没有任何意义。 If some SUT expects list of dependencies, then you should put mock objects (accessible via Object property of mock) into that list. 如果某些SUT需要依赖项列表,则应将模拟对象(可通过模拟的Object属性访问)放入该列表中。 That's the difference between Moq and RhinoMocks. 这是Moq和RhinoMocks之间的区别。

Thank you, @SergeyBerezovskiy. 谢谢@SergeyBerezovskiy。 I was not even in a right direction. 我什至没有朝着正确的方向前进。 Thank you for the ref link. 感谢您的参考链接。 I rewrote the example of the link in C# version and tested. 我重写了C#版本中的链接示例并进行了测试。 I think it help me understand Mock a lot. 我认为这对我了解Mock很有帮助。 Please let me know if I made anything bad in here. 如果我在这里有什么不好的事情,请告诉我。 Thanks. 谢谢。

WareHouse.cs WareHouse.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test {
    public class WareHouse {
    private Dictionary<string, int> wareHouseRepo = new Dictionary<string, int>();

    public void Add(string location, int number) {
    //null add
    if (wareHouseRepo.ContainsKey(location)) {
    int inventory_Old = wareHouseRepo[location];
        wareHouseRepo[location] = inventory_Old + number;
    } else {
         wareHouseRepo.Add(location, number);
    }
}


public virtual bool FillIt(string location, int number) {
    if (wareHouseRepo.ContainsKey(location)) {
        int inventory = wareHouseRepo[location];
        if(inventory >= number){
            wareHouseRepo[location] = inventory - number;
            return true;
        }else{
            return false;
        }
    } else {
        return false;
    }
}
}    

public int GetInventory(string location) {
    if (wareHouseRepo.ContainsKey(location)) {
        return wareHouseRepo[location];              
    } else {
        return 0;
    }
    }
}

Order.cs Order.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

    namespace Test {
        class Order {
            private string location = "";
            private int orderNum = 0;

            private bool filled = true;

            public Order(string loc, int num) {
                this.location = loc;
                this.orderNum = num;
            }

            public void Fill(WareHouse wh){
                if (wh.FillIt(location, orderNum)) {
                    filled = true;
                } else {
                    filled = false;
                }
            }

            public bool isFilled() {
                return filled;
            }

        }
    }

TestWareHouse.cs TestWareHouse.cs

using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test {

    [TestFixture]
    class TestWareHouse {

        private WareHouse warehouse = new WareHouse();
        private static String TALISKER = "Talisker";
        private static String HIGHLAND_PARK = "Highland Park";

        [SetUp]
        public void SetUp() {
            //reset
            warehouse = new WareHouse();
            warehouse.Add(TALISKER, 50);
            warehouse.Add(HIGHLAND_PARK, 25);
        }

        //regular testing
        [Test]
        public void testOrderIsFilledIfEnoughInWarehouse() {
            Order order = new Order(TALISKER, 50);
            order.Fill(warehouse);
            Assert.True(order.isFilled());
            Assert.AreEqual(0, warehouse.GetInventory(TALISKER));
        }

         [Test]
        public void testOrderDoesNotRemoveIfNotEnough() {
            Order order = new Order(TALISKER, 51);
            order.Fill(warehouse);
            Assert.False(order.isFilled());
            Assert.AreEqual(50, warehouse.GetInventory(TALISKER));
        }


        //Now I am trying to do the things with Mock
        [Test]
        public void testOrderIsFilledIfEnoughInWarehouseMock() {
            Order order = new Order(TALISKER, 50);
            //-- Creating a fake ICustomerRepository object
            var warehouseMock = new Mock<WareHouse>();
            //warehouseMock

            warehouseMock
            .Setup(m => m.FillIt(TALISKER, 50))
            .Returns(true);

            order.Fill(warehouseMock.Object);


            //-- Assert ----------------------
            Assert.IsTrue(order.isFilled());


            warehouseMock.Verify(x => x.FillIt(It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(1));
            }

        [Test]
        public void testFillingDoesNotRemoveIfNotEnoughInStock() {
            Order order = new Order(TALISKER, 51);
            //-- Creating a fake ICustomerRepository object
            var warehouseMock = new Mock<WareHouse>();

             warehouseMock
            .Setup(m => m.FillIt(It.IsAny<string>(), It.IsAny<int>()))
            .Returns(false);

             order.Fill(warehouseMock.Object);


            //-- Assert ----------------------
            Assert.IsFalse(order.isFilled());

            warehouseMock.Verify(x => x.FillIt(It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(1));

        }

      }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM