【问题标题】:Mocking DbContext - can't insert new objects模拟 DbContext - 无法插入新对象
【发布时间】:2015-05-11 20:34:48
【问题描述】:

https://msdn.microsoft.com/en-us/data/dn314429.aspx

我正在尝试使用类似于上面链接中的查询场景的代码。 英孚 6.1 + 起订量。 它适用于这样的预填充数据(我可以毫无问题地查询它们):

_context = new Mock<MyContext>();

IQueryable<users> users = new List<users>
        {
            new users{id = 1, email = "test@test.pl", password = "test", created = DateTime.Now, modified = DateTime.Now},
            new users{id = 2, email = "test2@test.pl", password = "test", created = DateTime.Now, modified = DateTime.Now},
        }.AsQueryable();
var mockUsers = new Mock<DbSet<users>>();
        mockUsers.As<IQueryable<users>>().Setup(m => m.Provider).Returns(users.Provider);
        mockUsers.As<IQueryable<users>>().Setup(m => m.Expression).Returns(users.Expression);
        mockUsers.As<IQueryable<users>>().Setup(m => m.ElementType).Returns(users.ElementType);
        mockUsers.As<IQueryable<users>>().Setup(m => m.GetEnumerator()).Returns(users.GetEnumerator());

_context.Setup(m => m.users).Returns(mockUsers.Object);

但是当我尝试在我的测试方法中添加新对象时,它不起作用:

[TestMethod]
    public void AddUser()
    {
        _context.Object.users.Add(new users { email = "test3@test.pl", password = "test", created = DateTime.Now, modified = DateTime.Now });
        _context.Object.SaveChanges();
        var count = _context.Object.users.Count();
        Assert.AreEqual(3, count); // count == 2 instead 3
    }

是否可以对其进行修改,这样我的模拟上下文实际上会应用 SaveChanges?我的想法是正确的还是由于某种原因这种方法是错误的?

如果我使用非查询方法,我将无法使用:

mockContext.Object.users.Count()

假设我有方法可以测试,片段如下:

 context.cards.Add(card);
 context.SaveChanges();
 SetCardForUser(card.id, user);

 protected void SetCardForUser(long cardId, users user)
    {
        user.card_id = cardId;
        context.SaveChanges();
    }

我需要在将对象插入数据库后返回 card.id。 我是否应该严格遵守 SRP 并拥有只做一件事的方法?

如果只有 AddCardAndStartAuthCharge 是公开的而其他方法受到保护,我将如何测试此方法? :

public async Task<long> AddCardAndStartAuthCharge(CardModel model, string username)
    {
        var hash = await GetHashForCardAuth(model);
        var CardWithId = AddCardToDatabase(model, username);
        long chargeId = AddHashToCard(CardWithId, hash);
        return chargeId;
    }

【问题讨论】:

  • 如果您查看,您发布的链接中有一个Add 场景:测试非查询场景
  • 我编辑了我的问题以更好地解释我的问题。
  • 我认为这里的主要问题是您说您需要修改卡ID并在保存后检索对象的ID。 id 由 EF 处理,根据我的经验,不需要自己修改或使用相应的属性。为什么需要这样做?
  • 这和我的另一个问题有关stackoverflow.com/questions/28901691/…

标签: c# entity-framework moq


【解决方案1】:

正如 cmets 中提到的,使用 EntityFramework 时绝对不需要自己管理 ID。观察这段代码sn-p:

MyContext context = new MyContext();
User user = new User() { Name = "Name1" };
context.Users.Add(user);
context.SaveChanges();
Console.WriteLine(user.Id);

执行SaveChanges 后会自动获得一个ID。因此,可以轻松重构与您的其他问题中的 sn-ps 类似的 sn-ps。

//Old
public CardModel AddCard(CardModel model, string username)
{
    var user = context.users.Where(x => x.email.ToLower() == username.ToLower()).First();
    var card = new cards()
    {
        created = DateTime.Now,
        modified = DateTime.Now,
        name = model.Name,
        type = model.CardType,
        user_id = user.id
    };
    context.cards.Add(card);
    context.SaveChanges();
    model.Id = card.id;
    return model;
}

//New
public CardModel AddCard(CardModel model, string username)
{
    var user = context.users.Where(x => x.email.ToLower() == username.ToLower()).First();
    user.Cards.Add(model)
    user.SaveOrUpdate()
    return model;
}

请注意,在任何时候都不需要像 model.Id = card.id 这样的东西!

如果不对现有模型进行一些更改,这很可能无法正常工作。您必须配置 EntityFramework 以将模型中的更改正确级联到所有子模型(例如,如果删除了 User,则删除所有 Cards)。

现在,回答你的问题。如果您根据上述重构代码,您将永远不会依赖上下文中包含的SaveChanges() 和DBSets 来提供模型。

由于您使用的是模拟,因此 ID 也不会更改,但鉴于您的代码,您不应该明确使用任何 ID。

您已经拥有的模拟就足够了,因为您不必为了使用它而从所述上下文中检索刚刚添加到上下文中的模型。记住这一点,既没有必要模拟SaveChanges 的后果,也没有必要模拟Count 之类的东西。

【讨论】:

  • 如果我不直接使用 Automapper 或实体,在这种情况下是否可以使用 user.Cards.Add(model)?通常我在这个例子中使用像 CardModel 这样的视图模型和像卡片这样的实体,并在它们之间手动或使用 automapper/linq 选择进行映射。
  • @Adiqq 嗯,这真的取决于你的具体情况。就目前而言:没有。
【解决方案2】:

是的,可以模拟保存方法。请看这个:

IQualityIssuesRepository qir = new QualityIssuesRepository();
        Mock<IQualityIssuesRepository> qualityIssuesRepository = new Mock<IQualityIssuesRepository>();
qualityIssuesRepository.CallBase = true;
Action<List<int>, int> saveNotification = qir.SaveNotification;
qualityIssuesRepository.Setup(p => p.SaveNotification(It.IsAny<List<int>>(), It.IsAny<int>()))
            .Callback(saveNotification);

您应该在模拟存储库中设置 Save 方法。只需创建该存储库的实例,并将该方法分配给模拟存储库。希望对你有帮助:)

【讨论】:

    【解决方案3】:

    考虑在更高的抽象级别上模拟,然后与您使用的工具紧密耦合。

    也许您的视图模型应该依赖于服务,而不是您使用的工具的详细信息(即 IIsesServiceChannel)。

    这是一个例子:

    我通常使用 IServices、Services 和 MockServices。

    • IServices 提供所有业务逻辑必须调用方法的可用操作。
    • 服务是我的代码隐藏注入视图模型(即实际数据库)的数据访问层。
    • MockServices 是我的单元测试注入到视图模型(即模拟数据)的数据访问层。

    IServices:

    public interface IServices
    {
        IEnumerable<Warehouse> LoadSupply(Lookup lookup);
        IEnumerable<Demand> LoadDemand(IEnumerable<string> stockCodes, int daysFilter, Lookup lookup);
    
        IEnumerable<Inventory> LoadParts(int daysFilter);
        Narration LoadNarration(string stockCode);
        IEnumerable<PurchaseHistory> LoadPurchaseHistory(string stockCode);
    
        IEnumerable<StockAlternative> LoadAlternativeStockCodes();
        AdditionalInfo GetSupplier(string stockCode);
    }
    

    模拟服务:

    public class MockServices : IServices
    {
        #region Constants
        const int DEFAULT_TIMELINE = 30;
        #endregion
    
        #region Singleton
        static MockServices _mockServices = null;
    
        private MockServices()
        {
        }
    
        public static MockServices Instance
        {
            get
            {
                if (_mockServices == null)
                {
                    _mockServices = new MockServices();
                }
    
                return _mockServices;
            }
        }
        #endregion
    
        #region Members
        IEnumerable<Warehouse> _supply = null;
        IEnumerable<Demand> _demand = null;
        IEnumerable<StockAlternative> _stockAlternatives = null;
        IConfirmationInteraction _refreshConfirmationDialog = null;
        IConfirmationInteraction _extendedTimelineConfirmationDialog = null;
        #endregion
    
        #region Boot
        public MockServices(IEnumerable<Warehouse> supply, IEnumerable<Demand> demand, IEnumerable<StockAlternative> stockAlternatives, IConfirmationInteraction refreshConfirmationDialog, IConfirmationInteraction extendedTimelineConfirmationDialog)
        {
            _supply = supply;
            _demand = demand;
            _stockAlternatives = stockAlternatives;
            _refreshConfirmationDialog = refreshConfirmationDialog;
            _extendedTimelineConfirmationDialog = extendedTimelineConfirmationDialog;
        }
    
        public IEnumerable<StockAlternative> LoadAlternativeStockCodes()
        {
            return _stockAlternatives;
        }
    
        public IEnumerable<Warehouse> LoadSupply(Lookup lookup)
        {
            return _supply;
        }
    
        public IEnumerable<Demand> LoadDemand(IEnumerable<string> stockCodes, int daysFilter, Syspro.Business.Lookup lookup)
        {
            return _demand;
        }
    
        public IEnumerable<Inventory> LoadParts(int daysFilter)
        {
            var job1 = new Job() { Id = Globals.jobId1, AssembledRequiredDate = DateTime.Now, StockCode = Globals.stockCode100 };
            var job2 = new Job() { Id = Globals.jobId2, AssembledRequiredDate = DateTime.Now, StockCode = Globals.stockCode200 };
            var job3 = new Job() { Id = Globals.jobId3, AssembledRequiredDate = DateTime.Now, StockCode = Globals.stockCode300 };
    
            return new HashSet<Inventory>()
            {
                new Inventory() { StockCode = Globals.stockCode100, UnitQTYRequired = 1, Category = "Category_1", Details = new PartDetails() { Warehouse = Globals.Instance.warehouse1, Job = job1} },
                new Inventory() { StockCode = Globals.stockCode200, UnitQTYRequired = 2, Category = "Category_1", Details = new PartDetails() { Warehouse = Globals.Instance.warehouse1, Job = job2} },
                new Inventory() { StockCode = Globals.stockCode300, UnitQTYRequired = 3, Category = "Category_1", Details = new PartDetails() { Warehouse = Globals.Instance.warehouse1, Job = job3} },
            };
        }
        #endregion
    
        #region Selection
        public Narration LoadNarration(string stockCode)
        {
            return new Narration()
            {
                Text = "Some description"
            };
        }
    
        public IEnumerable<PurchaseHistory> LoadPurchaseHistory(string stockCode)
        {
            return new List<PurchaseHistory>();
        }
    
        public AdditionalInfo GetSupplier(string stockCode)
        {
            return new AdditionalInfo()
            {
                SupplierName = "Some supplier name"
            };
        }
        #endregion
    
        #region Creation
        public Inject Dependencies(IEnumerable<Warehouse> supply, IEnumerable<Demand> demand, IEnumerable<StockAlternative> stockAlternatives, IConfirmationInteraction refreshConfirmation = null, IConfirmationInteraction extendedTimelineConfirmation = null)
        {
            return new Inject()
            {
                Services = new MockServices(supply, demand, stockAlternatives, refreshConfirmation, extendedTimelineConfirmation),
    
                Lookup = new Lookup()
                {
                    PartKeyToCachedParts = new Dictionary<string, Inventory>(),
                    PartkeyToStockcode = new Dictionary<string, string>(),
                    DaysRemainingToCompletedJobs = new Dictionary<int, HashSet<Job>>(),
    .
    .
    .
    
                },
    
                DaysFilterDefault = DEFAULT_TIMELINE,
                FilterOnShortage = true,
                PartCache = null
            };
        }
    
        public List<StockAlternative> Alternatives()
        {
            var stockAlternatives = new List<StockAlternative>() { new StockAlternative() { StockCode = Globals.stockCode100, AlternativeStockcode = Globals.stockCode100Alt1 } };
            return stockAlternatives;
        }
    
        public List<Demand> Demand()
        {
            var demand = new List<Demand>()
            {
                new Demand(){ Job = new Job{ Id = Globals.jobId1, StockCode = Globals.stockCode100, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode100, RequiredQTY = 1}, 
                new Demand(){ Job = new Job{ Id = Globals.jobId2, StockCode = Globals.stockCode200, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode200, RequiredQTY = 2}, 
            };
            return demand;
        }
    
        public List<Warehouse> Supply()
        {
            var supply = new List<Warehouse>() 
            { 
                Globals.Instance.warehouse1, 
                Globals.Instance.warehouse2, 
                Globals.Instance.warehouse3,
            };
            return supply;
        }
        #endregion
    }
    

    服务:

    public class Services : IServices
    {
        #region Singleton
        static Services services = null;
    
        private Services()
        {
        }
    
        public static Services Instance
        {
            get
            {
                if (services == null)
                {
                    services = new Services();
                }
    
                return services;
            }
        }
        #endregion
    
        public IEnumerable<Inventory> LoadParts(int daysFilter)
        {
            return InventoryRepository.Instance.Get(daysFilter);
        }
    
        public IEnumerable<Warehouse> LoadSupply(Lookup lookup)
        {
            return SupplyRepository.Instance.Get(lookup);
        }
    
        public IEnumerable<StockAlternative> LoadAlternativeStockCodes()
        {
            return InventoryRepository.Instance.GetAlternatives();
        }
    
        public IEnumerable<Demand> LoadDemand(IEnumerable<string> stockCodes, int daysFilter, Lookup lookup)
        {
            return DemandRepository.Instance.Get(stockCodes, daysFilter, lookup);
        }
    .
    .
    .
    

    单元测试:

        [TestMethod]
        public void shortage_exists()
        {
            // Setup
            var supply = new List<Warehouse>() { Globals.Instance.warehouse1, Globals.Instance.warehouse2, Globals.Instance.warehouse3 };
            Globals.Instance.warehouse1.TotalQty = 1;
            Globals.Instance.warehouse2.TotalQty = 2;
            Globals.Instance.warehouse3.TotalQty = 3;
    
            var demand = new List<Demand>()
            {
                new Demand(){ Job = new Job{ Id = Globals.jobId1, StockCode = Globals.stockCode100, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode100, RequiredQTY = 1}, 
                new Demand(){ Job = new Job{ Id = Globals.jobId2, StockCode = Globals.stockCode200, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode200, RequiredQTY = 3}, 
                new Demand(){ Job = new Job{ Id = Globals.jobId3, StockCode = Globals.stockCode300, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode300, RequiredQTY = 4}, 
            };
    
            var alternatives = _mock.Alternatives();
            var dependencies = _mock.Dependencies(supply, demand, alternatives);
    
            var viewModel = new MainViewModel();
            viewModel.Register(dependencies);
    
            // Test
            viewModel.Load();
    
            AwaitCompletion(viewModel);
    
            // Verify
            var part100IsNotShort = dependencies.PartCache.Where(p => (p.StockCode == Globals.stockCode100) && (!p.HasShortage)).Single() != null;
            var part200IsShort = dependencies.PartCache.Where(p => (p.StockCode == Globals.stockCode200) && (p.HasShortage)).Single() != null;
            var part300IsShort = dependencies.PartCache.Where(p => (p.StockCode == Globals.stockCode300) && (p.HasShortage)).Single() != null;
    
            Assert.AreEqual(true, part100IsNotShort &&
                                    part200IsShort &&
                                    part300IsShort);
        }
    

    CodeBehnd:

        public MainWindow()
        {
            InitializeComponent();
    
            this.Loaded += (s, e) =>
                {
                    this.viewModel = this.DataContext as MainViewModel;
    
                    var dependencies = GetDependencies();
                    this.viewModel.Register(dependencies);
    .
    .
    .
    

    视图模型:

        public MyViewModel()
        {
    .
    .
    .
        public void Register(Inject dependencies)
        {
            try
            {
                this.Injected = dependencies;
    
                this.Injected.RefreshConfirmation.RequestConfirmation += (message, caption) =>
                    {
                        var result = MessageBox.Show(message, caption, MessageBoxButton.YesNo, MessageBoxImage.Question);
                        return result;
                    };
    
                this.Injected.ExtendTimelineConfirmation.RequestConfirmation += (message, caption) =>
                    {
                        var result = MessageBox.Show(message, caption, MessageBoxButton.YesNo, MessageBoxImage.Question);
                        return result;
                    };
    
    .
    .
    .
            }
    
            catch (Exception ex)
            {
                Debug.WriteLine(ex.GetBaseException().Message);
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2018-08-15
      • 1970-01-01
      • 2017-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-30
      相关资源
      最近更新 更多