MOQ测试电子邮件服务,无法收回价值

时间:2018-08-14 09:05:33

标签: c# unit-testing moq abstract-class

我正在尝试测试域服务,该服务将在下订单后发送电子邮件。该服务具有私有方法,因此我在公共接口上调用了一个称为私有服务方法的方法。问题是我似乎无法检查电子邮件上的抄送,因为这是私有方法。 我知道唯一的办法来解决这个问题,如果该值被保存为接口属性等,但事实并非如此。参见下面的代码。

     public int SendConsolidatedDespatchNotifications(int countOfWorkDays)
    {
        var sent = 0;
        var trackings = _despatchLineRepository.GetTrackingWithoutDespatchNotificationInPreviousWorkDays(countOfWorkDays);
        var trackingsWithinOrder = trackings == null
            ? new List<OrderLineTracking>()
            : trackings.Where(dl => dl.DespatchReference != null).ToList();
        trackingsWithinOrder.GroupBy(ot => ot.OrderKey).ForEach(
            ot =>
            {
                if (SendConsolidatedDespatchNotifications(ot))
                {
                    _despatchLineRepository.SetAsSent(ot.Select(ol => ol.DespatchLine));
                    sent++;
                }
            });

        return sent;
    }

     private bool SendConsolidatedDespatchNotifications(IGrouping<int, OrderLineTracking> orderTrackingLines)
    {
        if (orderTrackingLines == null)
            return false;
        if (orderTrackingLines.Key == 0)
            return false;
        if (orderTrackingLines.Any())
        {
            var firstLine = orderTrackingLines.First();
            var allOrderLines = _orderLineRepository.GetOrderLinesByOrderKey(firstLine.OrderKey);
            var partiallyDespatchedLines = FindPartiallyDespatchedLines(orderTrackingLines);
            var notDespatchedLines = FindNotDespatchedLines(allOrderLines, orderTrackingLines);
            return SendConsolidatedDespatchedEmail(firstLine.DespatchReference, orderTrackingLines, partiallyDespatchedLines, notDespatchedLines);
        }

        return false;
    }

    private bool SendConsolidatedDespatchedEmail(
        string poNumber,
        IEnumerable<OrderLineTracking> despatchedLines,
        IEnumerable<OrderLineTracking> partiallyDespatchedLines,
        IEnumerable<OrderLine> notDespatchLines)
    {
        //we just assume that one PO have always just one order
        var firstDespatchedLine = despatchedLines.First();
        var order = firstDespatchedLine.OrderLine.OrderHeader;
        if (order?.Customer == null)
            return false;

        var despatchGroups = new List<DespatchLineGroup>();
        despatchedLines.GroupBy(dl => dl.DespatchReference).ForEach(
            dl => despatchGroups.Add(
                new DespatchLineGroup
                {
                    DespatchReference = dl.Key,
                    DespatchedLines = dl,
                    TrackingWebLink = GetTrackingWebLinkFor(dl.First())
                }));

        var despatchNotificationEmail = new DespatchConsolidatedNotificationEmail(
            order.Customer,
            order,
            despatchGroups,
            CreateNotDespatchedItemsList(partiallyDespatchedLines, notDespatchLines));

        var ccCustomer = _customerRepository.GetByCostCentreIdentifier(order.CostCentreIdentifier, order.Customer.Key);

        var ccOnBasket = ccCustomer?.CostCentre; 

        if (ccOnBasket == null)
        {
            despatchNotificationEmail.To.Add(new EmailAddress(order.Customer.FullName, order.Customer.Login));
        }
        else
        {
            FillInSubaccountDetails(despatchNotificationEmail, ccCustomer, order, order.Customer, ccOnBasket);
        }

        despatchNotificationEmail.PopulateContentWithTags();
        despatchNotificationEmail.SendAfter = firstDespatchedLine.DespatchDate;
        despatchNotificationEmail.Save();

        _log.InfoFormat("Despatch email {0} for {2} sent to {1}", "DespatchConsolidatedNotificationEmail", order.Customer.Login, poNumber);

        return true;
    }

    private void FillInSubaccountDetails(
        EmailTemplate email,
        Customer ccCustomer,
        OrderHeader order,
        Customer masterAccount,
        CostCentre ccOnBasket)
    {
        //send notifications to CostCentre account, which is on basket
        email.To.Add(new EmailAddress(ccCustomer.FullName, ccCustomer.Login));

        if (ccOnBasket.ReceiveNotifications) //send notifications to master only if CC is set so
        {
            email.To.Add(new EmailAddress(masterAccount.FullName, masterAccount.Login));
        }

        if (order.OrderPlacedBy.HasValue) //PD-2140 Sending email to Purchaser as well
        {
            var purchaser = _customerRepository.Get(order.OrderPlacedBy.Value);
            if (purchaser?.Purchaser != null && purchaser.Purchaser.ReceiveNotifications)
            {
                email.To.Add(new EmailAddress(purchaser.FullName, purchaser.Login));
            }
        }


        if ( order.ApprovedBy != null)
        {
            var approver = _customerRepository.Get(order.ApprovedBy.Value);
            if(approver?.Approver != null &&  //has approver and its not MAH
               approver.Approver.ReceiveNotifications)
                email.To.Add(new EmailAddress(approver.FullName, approver.Login));
        }
    }

   //this inherits from EmailTemplate which has save method.
   public class DespatchConsolidatedNotificationEmail : EmailTemplate
     {
    public DespatchConsolidatedNotificationEmail() { }

    public DespatchConsolidatedNotificationEmail(
        Customer customer,
        OrderHeader orderHeader,
        List<DespatchLineGroup> despatchLines,
        List<NotDespatchedLine> notDespatchLines)
    {
        AddEmailData(customer);
        AddEmailData(orderHeader);
        AddEmailData(despatchLines);
        AddEmailData(notDespatchLines);
    }
}
    //below is the save method
    public int Save()
    {
        var manageSave = Configuration.Container.Resolve<IWantToManageSaving>();
        return manageSave.Save(this);
    }

注意Email实现了一个抽象类,它是EmailTemplate而不是接口。

我想弄清楚添加了哪个emailAddress吗?

2 个答案:

答案 0 :(得分:0)

有赞成和反对单元测试专用方法的参数。我将由您决定是否是个好主意。话虽如此,您可以使用PrivateObject类。遵循以下原则:

Class c = new Class();
PrivateObject o = new PrivateObject(c);
var whatever = o.Invoke("FillInSubaccountDetails");
Assert.AreEqual(whatever, expected);

这里有一个问题,因为您的方法返回void,没有要声明的返回值。您可能需要调整方法?

答案 1 :(得分:0)

因此,根据您提供的所有代码:

您的Save方法闻起来很糟糕。您不应该使用IoC容器来手动解决依赖关系。如果您通过ctor注入了IWantToManageSaving(我喜欢这样命名:)),则可以在测试中对其进行模拟。如果您有var savingManagerMock = new Mock<IWantToManageSaving>(),则可以在单元测试中验证Save方法是用正确设置的EmailTemplate实例调用的。像这样:

// ASSERT

savingManagerMock.Verify(x => x.Save(It.IsAny<EmailTemplate>(
    arg => arg.To.Contains(/* ... */));

类似的东西,取决于您想要的实际断言。

另一种方法是将DespatchConsolidatedNotificationEmail的结构抽象到工厂IDespatchConsolidatedNotificationEmailFactory中,使其返回DespatchConsolidatedNotificationEmail的特殊模型,并设置其Save方法,例如,保存EmailTemplate的当前状态,然后声明它。但是,我仍然倾向于第一个解决方案。

最后,请注意:测试此方法相当复杂。这通常意味着它可以写得更好。在这种情况下,我看到两个危险信号,第一个是明确使用Container,这可以通过依赖项注入(手动解析而不是不注入:P)来避免。其次是这种方法相当复杂!它调用了许多私有方法,并且有很多逻辑无法通过快速阅读该方法来理解。您应该考虑将这些私有方法拆分为另一个类中的internal帮助方法,这些方法将分别进行单元测试。然后,您可以在测试此公共方法时信任它们,因为它们在那时基本上是一个依赖项,因此可以设置模拟程序并断言正确的internal方法已被调用并且该方法的协定已得到满足。