C# 使用 Moq 和存储库模式进行单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19766885/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 15:49:54  来源:igfitidea点击:

Unit Testing With Moq and Repository Pattern

c#unit-testing

提问by Mike Ross

I am new to unit testing and I would like some help. I am using code first with repository pattern. I have a generic repository which contains generic CRUD operation called Generic Repository ( see blow )

我是单元测试的新手,我需要一些帮助。我首先将代码与存储库模式一起使用。我有一个通用存储库,其中包含名为 Generic Repository 的通用 CRUD 操作(请参阅打击)

 public abstract class GenericRepository<T> where T : class
    {
        private HolidayDatabaseContext _dataContext;
        private readonly IDbSet<T> _dbset;
        protected GenericRepository(IDbFactory databaseFactory)
        {
            DatabaseFactory = databaseFactory;
            _dbset = DataContext.Set<T>();
        }
        protected IDbFactory DatabaseFactory
        {
            get;
            private set;
        }
        protected HolidayDatabaseContext DataContext
        {
            get { return _dataContext ?? (_dataContext = DatabaseFactory.Get()); }
        }

        public virtual void Add(T entity)
        {
            _dbset.Add(entity);
        }
        public virtual void Update(T entity)
        {
            _dataContext.Entry(entity).State = EntityState.Modified;
        }
        public virtual void Delete(T entity)
        {
            _dbset.Remove(entity);
        }
        public virtual IEnumerable<T> Enumerable()
        {
            return _dbset.AsEnumerable<T>();
        }

        public virtual IQueryable<T> List()
        {
            return _dbset.AsQueryable<T>();
        }

        public virtual T GetSingleById(int id)
        {
            return _dbset.Find(id);
        }

        public void Save()
        {
            _dataContext.SaveChanges();
        }

    }

I have then inherited it into a User Repository and created some specific methods. see below

然后我将它继承到一个用户存储库中并创建了一些特定的方法。见下文

public class UserRepository : GenericRepository<User>, IUserRepository
    {
        public UserRepository(IDbFactory databaseFactory)
            : base(databaseFactory) { }


        public int HolidayEntitlement(int userId)
        {
           return HolidayEntitlement(userId, DateTime.Now);
        }
        public int HolidayEntitlement(int userId, DateTime dateTime)
        {
           //Get the User
            var user = this.GetSingleById(userId);

            //Work Total Entitlement
            int entitlement = user.BaseHolidayEntitlement;

            //Years in Service
            entitlement += (dateTime - user.EmploymentStartDate).Days / 365;

            return entitlement;



        }


         public int RemainingHolidayEntitlement(int userId)
        {
            return RemainingHolidayEntitlement(userId, DateTime.Now);
        }

         public int RemainingHolidayEntitlement(int userId, DateTime dateTime)
        {
            return int.MinValue;
        }
    }

I would like to Unit test HolidayEntitlement(int userId, DateTime dateTime) but i need to mock the GetSingleById part in the method

我想对 HolidayEntitlement(int userId, DateTime dateTime) 进行单元测试,但我需要模拟方法中的 GetSingleById 部分

I have written this as a Test but it doesn't compile.

我已经把它写成一个测试,但它不能编译。

 [TestMethod]
        public void GetHolidayEntitlement25()
        {
            //How to write this Unit test


            //Setup
            var user = new User { AnnualHolidayIncrement = 1, BaseHolidayEntitlement = 25, EmploymentStartDate = new DateTime(2013, 1, 1),Id=1 };

            Mock<UserRepository> mock = new Mock<UserRepository>();
            mock.Setup(m => m.GetSingleById(1)).Returns(user);

            Assert.AreEqual(25, mock.Object.HolidayEntitlement(1));
        }

Any help would be appreciated

任何帮助,将不胜感激

采纳答案by Belogix

You seem to be saying that you only want to mock part of the interface. When you start encountering this sort of situation it suggests that you are mixing your concerns and probably doing something wrong somewhere.

您似乎是在说您只想模拟界面的一部分。当您开始遇到这种情况时,这表明您正在混合您的担忧,并且可能在某处做错了。

In this instance your Repository is doing MUCHmore than just CRUD and therefore has multiple responsibilities (it should only have one, lookup SOLID programming). You are performing Business logic in the repository and it should not live there! Anything other than simple CRUD operations should be moved out into the Business Logic Layer. I.e. your HolidayEntitlementmethod calculates something by applying some logic and is therefore NOT a CRUD / repository operation!

在这种情况下你的版本库做MUCH不仅仅是CRUD,因此具有多重责任(它应该只有一个,查找扎实的编程)。您正在存储库中执行业务逻辑,它不应该存在于那里!除了简单的 CRUD 操作之外的任何事情都应该移到业务逻辑层中。即你的HolidayEntitlement方法通过应用一些逻辑来计算一些东西,因此不是一个 CRUD/存储库操作!

So... What you should do is move the business logic bits out into a new class, say UserLogic. Within the UserLogicclass you would use an injected IUserRepositoryto communicate with your repository. In UserLogicthat is where you would put your HolidayEntitlementmethod and it would make a call to IUserRepository.GetSingleById. So, when you then test your UserLogicclass you would inject in your mock IUserRepositorythat would have the stub version of GetSingleByIdand then you will be back on the right track!

所以......你应该做的是将业务逻辑位移到一个新类中,比如UserLogic. 在UserLogic课程中,您将使用注入IUserRepository与您的存储库进行通信。在UserLogic那里你可以放置你的HolidayEntitlement方法,它会调用IUserRepository.GetSingleById. 因此,当您然后测试您的UserLogic类时,您将注入IUserRepository具有存根版本的模拟,GetSingleById然后您将回到正确的轨道上!

I hope that makes sense / helps?!

我希望这是有道理的/有帮助吗?!

--ORIGINAL POST--

——原帖——

P.S. My original post stated that you should mock interfaces, not instances so this still applies and I will leave here for reference:

PS 我原来的帖子说你应该模拟接口,而不是实例,所以这仍然适用,我会留在这里以供参考:

You should be mocking IUserRepositoryNOTUserRepository.

你应该嘲笑IUserRepositoryNOTUserRepository

That is because UserRepositoryis an implementation of IUserRepository. You want to say that you are giving it a NEW implementation, i.e. your mock. At the moment you are using the ACTUALclass UserRepository.

那是因为UserRepository是 的实现IUserRepository。你想说你正在给它一个新的实现,即你的模拟。目前您正在使用ACTUALUserRepository

回答by Stephen Byrne

Mocking is generally used when you need to supply a fake dependencyand in this case you appear to be trying to Mock the System Under Test (SUT) which doesn't really make sense - there's literally no point because your test is not actually telling you anything about the behaviour of UserRepository; all you are doing is testing if you setup your Mock correctly which isn't very useful!

当您需要提供虚假依赖项时,通常会使用模拟,在这种情况下,您似乎是在尝试模拟被测系统 (SUT),这实际上没有意义——实际上没有意义,因为您的测试实际上并没有告诉您关于行为的任何事情UserRepository;您所做的只是测试您是否正确设置了 Mock,这不是很有用!

The test code you have given seems to indicate that you want to test UserRepository.HolidayEntitlement.

您提供的测试代码似乎表明您要测试UserRepository.HolidayEntitlement.

I would be much more inclined to move functions like that out of your Repository class and into a separate business-logic type class. This way you can test the logic of calculating a user's holiday entitlement in total isolation which is a major principle of unit testing.

我更倾向于将这样的功能从您的 Repository 类中移出并移到一个单独的业务逻辑类型类中。通过这种方式,您可以完全隔离地测试计算用户假期权利的逻辑,这是单元测试的主要原则。

In order to test that this function does what it's supposed to do (i.e perform a calculation based on properties of a User) you need to ensure that whatever Userinstance is being operated on within that function is 100% isolated and under your control - either with a Mock or Fake (Stub) instance of User, in this case Mocks are an excellent choice because you only need to implement the parts of the dependency that your SUT is going to need.

为了测试这个函数做它应该做的事情(即根据 a 的属性执行计算User),您需要确保User在该函数内操作的任何实例都是 100% 隔离的并且在您的控制之下 - 或者使用User 的 Mock 或 Fake (Stub) 实例,在这种情况下 Mocks 是一个很好的选择,因为您只需要实现 SUT 将需要的依赖项部分。

So, what you could do is this:

所以,你可以做的是:

Define an interface for User

为用户定义接口

public interface IUser
{
  int BaseHolidayEntitlement{get;set;}
  DateTime EmploymentStartDate {get;set;}
  //other properties for a User here
}

Implement this on your User class

在你的 User 类上实现这个

public class User:IUser
{
   //implemement your properties here
   public int BaseHolidayEntitlement{get;set;}
   public DateTime EmploymentStartDate {get;set;}
   //and so on
}

Create a class for User logic

为用户逻辑创建一个类

public class UserRules
{
  public int GetHolidayEntitlement(IUser user,DateTime dateTime)
  {
     //perform your logic here and return the result
  }
}

Now your test becomes much simpler and doesn't even need the repository

现在您的测试变得更加简单,甚至不需要存储库

 [TestMethod]
 public void GetHolidayEntitlement_WithBase25_Returns25()
 {
    //Arrange
    var user = new Mock<IUser>();
    //setup known, controlled property values on the mock:
    user.SetupGet(u=>u.BaseHolidayEntitlement).Returns(25);
    user.SetupGet(u=>u.EmploymentStartDate).Returns(new DateTime(2013,1,1));
    var sut = new UserRules();
    int expected = 25;
    //Act
    int actual = sut.GetHolidayEntitlement(user.Object,DateTime.UtcNow);
    //Assert
    Assert.AreEqual(expected,actual,"GetHolidayEntitlement isn't working right...");
 }