我是单元测试的新手,我想知道为什么它不起作用

时间:2018-11-27 13:45:17

标签: asp.net-mvc unit-testing xunit

我是MVC和单元测试的新手,因此我一直遵循指南等。

此刻,我正在研究单元测试。据我所知,我有一个测试应该可以工作,但不幸的是,不能。

    using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using WorkingWithVisualStudio.Controllers.Home;
using WorkingWithVisualStudio.Models;
using Xunit;
namespace WorkingWithVisualStudio.Tests
{
    public class HomeControllerTests
    {
        class ModelCompleteFakeRepository : IRepository
        {
            public IEnumerable<Product> Products { get; } = new Product[] {
 new Product { Name = "P1", Price = 275M },
 new Product { Name = "P2", Price = 48.95M },
 new Product { Name = "P3", Price = 19.50M },
 new Product { Name = "P3", Price = 34.95M }};
            public void AddProduct(Product p)
            {
                // do nothing - not required for test
            }
        }
        [Fact]
        public void IndexActionModelIsComplete()
        {
            // Arrange
            var controller = new HomeController();
            controller.Repository = new ModelCompleteFakeRepository();

            // Act
            var model = (controller.Index() as ViewResult)?.ViewData.Model
            as IEnumerable<Product>;
            // Assert
            Assert.Equal(controller.Repository.Products, model,
            Comparer.Get<Product>((p1, p2) => p1.Name == p2.Name
            && p1.Price == p2.Price));
        }
        class ModelCompleteFakeRepositoryPricesUnder50 : IRepository
        {
            public IEnumerable<Product> Products { get; } = new Product[] {
 new Product { Name = "P1", Price = 5M },
 new Product { Name = "P2", Price = 48.95M },
 new Product { Name = "P3", Price = 19.50M },
 new Product { Name = "P3", Price = 34.95M }};
            public void AddProduct(Product p)
            {
                // do nothing - not required for test
            }
        }
        [Fact]
        public void IndexActionModelIsCompletePricesUnder50()
        {
            // Arrange
            var controller = new HomeController();
            controller.Repository = new ModelCompleteFakeRepositoryPricesUnder50();
            // Act
            var model = (controller.Index() as ViewResult)?.ViewData.Model
            as IEnumerable<Product>;
            // Assert
            Assert.Equal(controller.Repository.Products, model,
            Comparer.Get<Product>((p1, p2) => p1.Name == p2.Name
            && p1.Price == p2.Price));
        }
    }
}

当我运行IndexActionModelIsCompletePricesUnder50()时  我得到以下信息:

Message: Assert.Equal() Failure
Expected: Product[] [Product { Name = "P1", Price = 5 }, Product { Name = "P2", Price = 48.95 }, Product { Name = "P3", Price = 19.50 }, Product { Name = "P3", Price = 34.95 }]
Actual:   ValueCollection<String, Product> [Product { Name = "Kayak", Price = 275 }, Product { Name = "Lifejacket", Price = 48.95 }, Product { Name = "Soccer ball", Price = 19.50 }, Product { Name = "Corner flag", Price = 34.95 }]

我的模型如下:

public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }

我的存储库:

public class SimpleRepository : IRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();

        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[]
            {
                new Product {Name = "Kayak", Price = 275M},
                new Product { Name = "Lifejacket", Price = 48.95M },
                new Product { Name = "Soccer ball", Price = 19.50M },
                new Product { Name = "Corner flag", Price = 34.95M }
            };
            foreach(var p in initialItems)
            {
                AddProduct(p);
            }
            //products.Add("Error", null);
        }

        public IEnumerable<Product> Products => products.Values;

        public void AddProduct(Product p) => products.Add(p.Name, p);
    }

我的存储库界面

public interface IRepository
{
    IEnumerable<Product> Products { get; }
    void AddProduct(Product p);
}

我的比较者:

public class Comparer
{
    public static Comparer<U> Get<U>(Func<U, U, bool> func)
    {
        return new Comparer<U>(func);
    }
}
public class Comparer<T> : Comparer, IEqualityComparer<T>
{
    private Func<T, T, bool> comparisonFunction;

    public Comparer(Func<T, T, bool> func)
    {
        comparisonFunction = func;
    }

    public bool Equals(T x, T y)
    {
        return comparisonFunction(x, y);
    }

    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }
}

我的控制器:

public class HomeController : Controller
{
    public IRepository Repository = SimpleRepository.SharedRepository;

    public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);

    [HttpGet]
    public IActionResult AddProduct() => View(new Product());

    [HttpPost]
    public IActionResult AddProduct(Product p)
    {
        Repository.AddProduct(p);
        return RedirectToAction("Index");
    }
}

很抱歉,这似乎是一个愚蠢的问题,但是我才刚刚开始研究单元测试。如果有人可以向我解释什么是问题,我一定会感激的。非常感谢您抽出宝贵的时间来帮忙。

2 个答案:

答案 0 :(得分:4)

我首先建议您使用Explicit Dependency Principle

重构控制器,使其遵循更坚固的方法
  

方法和类应明确要求(通常通过方法参数或构造函数参数)它们所需的任何协作对象,以使其正常运行。

所以控制器最终看起来像这样

public class HomeController : Controller {
    private readonly IRepository repository;

    public HomeController(IRepository repository) {
        this.repository = repository;
    }

    public IActionResult Index() => View(repository.Products.ToList());

    [HttpGet]
    public IActionResult AddProduct() => View(new Product());

    [HttpPost]
    public IActionResult AddProduct(Product p) {
        repository.AddProduct(p);
        return RedirectToAction("Index");
    }
}

为避免最初在隔离的单元测试期间访问共享存储库时犯的错误,该错误导致断言失败。

请尝试避免将类与静态或共享依赖项紧密耦合。注入该依赖关系的抽象会更安全。

现在可以清楚地执行测试的简化版本。

class ModelCompleteFakeRepository : IRepository {
    public IEnumerable<Product> Products { get; } = new Product[] {
        new Product { Name = "P1", Price = 275M },
        new Product { Name = "P2", Price = 48.95M },
        new Product { Name = "P3", Price = 19.50M },
        new Product { Name = "P3", Price = 34.95M }
    };

    public void AddProduct(Product p) {
        // do nothing - not required for test
    }
}

[Fact]
public void IndexActionModelIsComplete() {
    // Arrange
    var repository = new ModelCompleteFakeRepository();
    var controller = new HomeController(repository);
    var expected = repository.Products;

    // Act
    var actual = (controller.Index() as ViewResult)?.ViewData.Model as IEnumerable<Product>;

    // Assert
    Assert.IsNotNull(actual);
    Assert.Equal(expected, actual);
}

答案 1 :(得分:2)

因为在您的Index方法中,您指的是SimpleRepository,而不是您的Repository成员。

替换

public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);

使用

public IActionResult Index() => View(Repository.Products);

我还应该添加您可能想看一下代码结构的信息,然后将存储库注入构造函数中。而且,您拥有的两个不同的测试都可以测试同一件事,因此仅需其中之一。

编辑:我的答案解决了您当前的问题,而@Nkosi答案显示了如何正确执行此操作。