如何对ViewModel消费方法进行单元测试?

时间:2013-10-24 15:15:54

标签: c# unit-testing viewmodel

我想测试一个以ViewModel为参数的辅助方法。我遇到的问题是测试似乎需要我实例化并分配我的ViewModel使用的所有模型。在下面给出的示例中,它并不是一个大问题,因为只有少数几个,但在实际的VM中,我正在研究它有一吨。还有其他方法可以做到这一点,所以我不必创建和分配每个对象吗?

用于说明目的的示例代码......

模特

public class Meal
{
    public int MealID { get; set; }

    public string Title { get; set; }
    public decimal Cost { get; set; }
}

public class Beverage
{
    public int BeverageID { get; set; }

    public string Title { get; set; }
    public decimal Cost { get; set; }
}

public class Desert
{
    public int DesertID { get; set; }

    public string Title { get; set; }
    public decimal Cost { get; set; }
}

ViewModel

public class DinnerViewModel
{
    public Meal Meal { get; set; }
    public Beverage Beverage { get; set; }
    public Desert Desert { get; set; }

    public decimal SalesTax { get; set; }
    public bool SeniorDiscount { get; set; }
}

助手

public class Calculator
{
    public decimal Total(DinnerViewModel dvm)
    {
        decimal subtotal = dvm.Meal.Cost + dvm.Beverage.Cost + dvm.Desert.Cost;

        if (dvm.SeniorDiscount)
        {
            subtotal = subtotal - (subtotal * 0.1M);
        }

        return subtotal + (subtotal * dvm.SalesTax);
    }
}

测试

[TestMethod]
public void CalculatorReturnsCorrectTotalForNonSenior()
{
    DinnerViewModel dvm = new DinnerViewModel();
    dvm.Meal.Cost = 7M;
    dvm.Beverage.Cost = 1M;
    dvm.Desert.Cost = 2M;
    dvm.SalesTax = 0.08M;
    dvm.SeniorDiscount = false;

    Calculator calc = new Calculator();

    decimal expected = 10.80M;
    decimal actual = calc.Total(dvm);

    Assert.AreEqual(expected, actual, "The actual value does not match the expected value.");
}

这会导致“NullReferenceException”错误。正如我所说,我可以创建并分配必要的对象......

[...]
Meal meal = new Meal();
dvm.Meal = meal;
dvm.Meal.Cost = 7M;
[...]

......而且,一旦完成它们,测试就会成功 - 但这对于更大的VM来说似乎有很多工作,我觉得我必须做些什么来使这更容易。< / p>

2 个答案:

答案 0 :(得分:2)

您会看到NullReferenceException,因为在您实例化它时,Meal,Beverage和Desert实例未分配给DinnerViewModel的相应属性。所以,所有这些属性都是null。您应该在访问这些属性之前创建和分配新对象。您可以使用视图模型的构造函数:

public class DinnerViewModel
{
    public DinnerViewModel()
    {
        Meal = new Meal();
        Beverage = new Beverage();
        Desert = new Desert();
    }

    public Meal Meal { get; set; }
    public Beverage Beverage { get; set; }
    public Desert Desert { get; set; }

    public decimal SalesTax { get; set; }
    public bool SeniorDiscount { get; set; }
}

此外,我还想创建一些辅助方法来返回测试的存根。它消除了重复并使您的测试变得清晰:

private DinnerViewModel CreateTenDollarsDinner()
{
    return new DinnerViewModel {
        Meal = new Meal { Cost = 7M },
        Beverage = new Beverage { Cost = 1M },
        Desert = new Desert { Cost = 2M },
        SalesTax = 0.08M,
        SeniorDiscount = false
    };
}

[TestMethod]
public void CalculatorReturnsCorrectTotalForNonSenior()
{
    DinnerViewModel dvm = CreateTenDollarsDinner();    
    Calculator calc = new Calculator();
    Assert.AreEqual(10.80M, calc.Total(dvm));
}

答案 1 :(得分:0)

您的模型(Meal,Beaverage和Dessert)也是ViewModel,或者您的DinnerViewModel实际上不是ViewModel。

ViewModel的目的是为View提供直接可用的值。

根据您的单元测试示例,将Calculator.Total辅助方法直接放到DinnerViewModel并将总计算值作为属性公开给视图更合适:

public class DinerViewModel
{
    public Meal Meal { get; set; }
    public Beverage Beverage { get; set; }
    public Desert Desert { get; set; }

    public decimal SalesTax { get; set; }
    public bool SeniorDiscount { get; set; }

    public decimal TotalCostOfDinner
    {
        get
        {
            decimal subtotal = 0M;
            if(Meal != null) subtotal += Meal.Cost;
            if(Beverage != null) subtotal += Beverage.Cost;
            if(Desert != null) subtotal += Desert.Cost;

            if (SeniorDiscount) subtotal -= subtotal * 0.1M;

            return subtotal + (subtotal * SalesTax);
        }
    }
}

现在,您的View可以直接从DinnerViewModel获取正确的TotalCostOfDinner。

现在进行单元测试:

[TestMethod]
public void TotalCostOfDinnerReturnsCorrectTotalForNonSenior()
{
    DinnerViewModel dvm = new DinnerViewModel
    {
        Meal = new Meal { Cost = 7M },
        Beverage = new Beverage { Cost = 1M },
        Desert = new Desert { Cost = 2M },
        SalesTax = 0.08M,
        SeniorDiscount = false
    };


    decimal expected = 10.80M;
    decimal actual = dvm.TotalCostOfDinner;

    Assert.AreEqual(expected, actual, "The actual value does not match the expected value.");
}