【问题标题】:Mocking with moq, trying to pass an object to constructor having multiple parameter模拟起订量,尝试将对象传递给具有多个参数的构造函数
【发布时间】:2014-02-19 05:17:08
【问题描述】:

我正在尝试模拟一个返回 IEnumerable 数据集的方法,例如所有代码的列表。

有一个接口 ISystemService.cs 包含此方法,一个名为 SystemService.cs 的服务类具有方法定义。

被测系统是:

 public static class CacheKeys
    {
      public const string ALLCURRENCYCODES = "CurrencyCodes";
    }
 public interface ICacheManager
    {
        T Get<T>(string key);
        void Set(string key, object data, int cacheTime);
        void Clear();
    }
public interface ISessionManager
{
}
public interface IApplicationSettings
    {
        string LoggerName { get; }
        int CacheTimeout { get; }
    }
public class EFDbContext : DbContext
    {
    public DbSet<CurrencyCode> CurrencyCodes { get; set; }
    }
public class CurrencyCode 
    {
        public string Code { get; set; }
        public string Description { get; set; }
        public decimal CurrencyUnit { get; set; }
        public int? DecimalPlace { get; set; }
        public string BaseCurrencyCode { get; set; }
    }   
public interface ISystemService
    {
        IEnumerable<CurrencyCode> GetAllCurrencyCodes();
    }
//SystemService.cs
public class SystemService : ISystemService
    {

        private readonly EFDbContext db;
        private readonly ICacheManager cacheManager;
        private readonly ISessionManager sessionManager;
        private readonly IApplicationSettings appSettings;

        public SystemService(EFDbContext dbContext, ICacheManager cacheManager, ISessionManager sessionManager, IApplicationSettings appSettings)
        {
            db = dbContext;
            this.cacheManager = cacheManager;
            this.sessionManager = sessionManager;
            this.appSettings = appSettings;
        }
      public IEnumerable<CurrencyCode> GetAllCurrencyCodes()
        {
            var allCurrencyCodes = cacheManager.Get<IEnumerable<CurrencyCode>>(CacheKeys.ALLCURRENCYCODES);

            if (allCurrencyCodes == null)
            {
                allCurrencyCodes = db.CurrencyCodes.ToList();

                cacheManager.Set(CacheKeys.ALLCURRENCYCODES, allCurrencyCodes, appSettings.CacheTimeout);
            }

            return allCurrencyCodes;
        }

测试方法

[TestMethod]
        public void testCacheMiss()
        {
            List<CurrencyCode> currencycodes = new List<CurrencyCode>()
         {
            new CurrencyCode(){Id = 1, Code = "IND", Description = "India"},
            new CurrencyCode(){Id = 2, Code = "USA", Description = "UnitedStates"},
            new CurrencyCodes(){Id = 3, Code = "UAE", Description = "ArabEmirates"}
         };
            var mockEfContext = new Mock<EFDbContext>();
            var mockCacheManager = new Mock<ICacheManager>();
            var mockSessionManager = new Mock<ISessionManager>();
            var mockAppSettings = new Mock<IApplicationSettings>();

            // Setups for relevant methods of the above here, e.g. to test a cache miss
            mockEfContext.SetupGet(x => x.CurrencyCodes)
               .Returns(currencycodes); // Canned currencies
            mockCacheManager.Setup(x => x.Get<IEnumerable<CurrencyCode>>(It.IsAny<string>()))
               .Returns<IEnumerable<CurrencyCodes>>(null); // Cache miss

            // Act
            var service = new SystemService(mockEfContext.Object, mockCacheManager.Object,
               mockSessionManager.Object, mockAppSettings.Object);
            var codes = service.GetAllCodes();

            // Assert + Verify
            mockCacheManager.Verify(x => x.Get<IEnumerable<CurrencyCodes>>(
               It.IsAny<string>()), Times.Once, "Must always check cache first");
            mockEfContext.VerifyGet(x => x.CurrencyCodes,
               Times.Once, "Because of the simulated cache miss, must go to the Db");
            Assert.AreEqual(currencycodes.Count, codes.Count(), "Must return the codes as-is");

既然定义的构造函数不接受一个参数,那么如何将对象作为参数传递呢?请指教

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    如果 CodeService 正在测试中,那么您想模拟它的依赖关系,而不是 CodeService 本身。

    您需要为构造函数提供CodeService 的所有依赖项Mocks,即:

         var currencycodes = new List<SomeCodes>
         {
            new CurrencyCodes(){Id = 1, Code = "IND", Description = "India"},
            new CurrencyCodes(){Id = 2, Code = "USA", Description = "UnitedStates"},
            new CurrencyCodes(){Id = 3, Code = "UAE", Description = "ArabEmirates"}
         };
         var mockEfContext = new Mock<EFDbContext>();
         var mockCacheManager = new Mock<ICacheManager>();
         var mockSessionManager = new Mock<ISessionManager>();
         var mockAppSettings = new Mock<IApplicationSettings>();
    
         // Setups for relevant methods of the above here, e.g. to test a cache miss
         mockEfContext.SetupGet(x => x.SomeCodes)
            .Returns(currencycodes); // Canned currencies
         mockCacheManager.Setup(x => x.Get<IEnumerable<SomeCodes>>(It.IsAny<string>()))
            .Returns<IEnumerable<SomeCodes>>(null); // Cache miss
    
         // Act
         var service = new CodeService(mockEfContext.Object, mockCacheManager.Object,
            mockSessionManager.Object, mockAppSettings.Object);
         var codes = service.GetAllCodes();
    
         // Assert + Verify
         mockCacheManager.Verify(x => x.Get<IEnumerable<SomeCodes>>(
            It.IsAny<string>()), Times.Once, "Must always check cache first");
         mockEfContext.VerifyGet(x => x.SomeCodes,
            Times.Once, "Because of the simulated cache miss, must go to the Db");
         Assert.AreEqual(currencycodes.Count, codes.Count(), "Must return the codes as-is");
    

    编辑但是,如果您的意思是代码的下一层正在测试中,则主体是相同的:

    var mockCodeService = new Mock<ICodeService>();
    mockCodeService.Setup(x => x.GetAllCodes())
        .Returns(currencycodes); // Now we don't care whether this is from cache or db 
    
    var higherLevelClassUsingCodeService = new SomeClass(mockCodeService.Object);
    higherLevelClassUsingCodeService.DoSomething();
    
    mockCodeService.Verify(x => x.GetAllCodes(), Times.Once); // etc
    

    编辑 2
    我已经修复了代码中的几个拼写错误,并假设 CurrencyCodes 继承 SomeCodes 并且您的缓存键是一个字符串,并将其推送到 Git Gist here 以及相应的缓存未命中单元测试。 (我用过 NUnit,但在这里并不重要)

    【讨论】:

    • CodeService.cs的GetAllCodes()方法正在测试中。所以我试图为类创建一个对象,比如 var service = new CodeService();然后对方法采取行动。这不正确吗?
    • 你想调用真正的GetAllCodes() 方法来测试它,否则你只是在测试模拟:)。在这种情况下,您要确保 GetAllCodes() 正确地利用缓存和 EF 上下文并与之交互,如上所述。需要返回存根数据的是Cache 和/或DbContext。以上是针对缓存未命中的,您还需要测试缓存命中,即如果缓存不返回 null,则不得调用 DbContext。一旦您测试了CodeService,您就可以Mock&lt;ICodeService&gt; 并测试您的代码的下一层。
    • 谢谢,试试看:)
    • '1 mockEfContext.SetupGet(x => x.SomeCodes) 2 .Returns(currencycodes); 3 mockCacheManager.Setup(x => x.Get>(It.IsAny()).Returns(null);' 对于上述行,我收到以下错误:ln1:最佳重载方法匹配对于 'Moq.Language.IReturnsGetter>.Returns(System.Data.Entity.DbSet)' 有一些无效参数 ln2:参数 1:无法从 'string' 转换为 'System.Data.Entity.DbSet'
    • 谢谢。是的,我开始了解起订量。由于我是新手,因此需要一些时间才能彻底了解它。请不要介意继续发布问题。感谢您的时间。
    【解决方案2】:

    allCodes 是您的服务.. 它是您需要使用的模拟。您不应该创建 ICodeService.. 的具体实例。您的模拟存在是为了填补该角色。

    所以,删除这个:

    var service = new CodeService(allCodes.object);
    

    你的下一行应该是:

    var code = allCodes.Object.GetAllCodes();
    

    但是..之后这个测试似乎完全多余..因为您似乎正在测试您的模拟..

    另外,allCodes 应该称为 serviceMock.. 因为这样更有意义。

    【讨论】:

    • 感谢您的意见。如果测试变得多余,请建议我应该如何继续模拟该方法。
    猜你喜欢
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 2011-11-28
    • 2020-08-11
    • 2011-10-24
    • 2013-12-12
    • 1970-01-01
    • 2014-06-04
    相关资源
    最近更新 更多