请我如何单元测试这个方法

本文关键字:方法 单元测试 | 更新日期: 2023-09-27 18:03:33

我有一个问题,试图单元测试这个方法。这对我来说似乎很清楚,但后来我很难理解如何有效地注入/模拟我的数组到bdcontext

public class UrlValidation: AbstractValidator<UrlShortenerModel> 
    {
        Uri uriResult;
        private EFDbContext context;
      .....
        public bool notExist(string url)
        {
            //Check if The Url Exist . This is to stop replication of Urls.
            bool exist = context.Urls.Any(x => x.OriginalUrl == url);
            return (exist == false);
        }
   ...
}

我想测试方法notExist。我从下面开始,然后卡住了如何模拟我的上下文的值到类?有什么方法可以表示或模仿上下文吗?我使用moq来模拟我的虚拟数据,但我只是没有看到这里的方式。而且它不是一个控制器类。

 public void Should_know_if_Url_Exist_or_Not()
        { 
            ArrayList arr = new ArrayList();
            arr.AddRange(new Url[]
            {
                new Url{ UrlId = 0, UrlCode = "TYUR", OriginalUrl="https://fluentvalidation.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 1, UrlCode = "TwUR", OriginalUrl="https://facebook.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 2, UrlCode = "TkUR", OriginalUrl="https://youtube.com/", IpAddress="127.0.0.1", PostedDate = DateTime.Now}
            });

            Assert.IsTrue(val.notExist("https://www.youtube.com/"));
            Assert.IsFalse(val.notExist("https://www.facebook.com/"));
        }

}

When i Changed my test to

 [TestMethod]
        public void Should_know_if_Url_Exist_or_Not()
        {
            Mock<IUrlsRepository> mock = new Mock<IUrlsRepository>();
            mock.Setup(u => u.Urls).Returns(new Url[] { 
                new Url{ UrlId = 0, UrlCode = "TYUR", OriginalUrl="https://fluentvalidation.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 1, UrlCode = "TwUR", OriginalUrl="https://facebook.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 2, UrlCode = "TkUR", OriginalUrl="https://youtube.com/", IpAddress="127.0.0.1", PostedDate = DateTime.Now}
            }.AsQueryable());

            var validator = new UrlValidation(mock.Object);

            Assert.IsTrue(validator.notExist("https://www.youtuberre.com/"));
            Assert.IsFalse(validator.notExist("https://www.facebook.com/"));
        }

并将验证类修改为

public IUrlShortenersRepository context;
public UrlValidation(IUrlShortenersRepository repo)
{
  context = repo;
...

我的测试运行没有问题,但是,它失败了。但我不能再次运行项目,因为我流畅的验证类需要一个参数,我不确定在哪里通过。我在模型中设置了验证类,如下所示

 [Validator(typeof(UrlValidation))]
    public class UrlShortenerModel
    {
        //The Model for the Home form. The Url shortener form.
        public string strUrl { get; set; }
        public IEnumerable<Url> urlList { get; set; }
    }

我不确定在哪里注入参数。

当我检查下面提供的链接时,我得到了一个清晰的例子,因此修改了我的测试
 [TestMethod]
        public void Should_know_if_Url_Exist_or_Not()
        {
            var arr = new List<Url>
            {
                new Url{ UrlId = 0, UrlCode = "TYUR", OriginalUrl="https://fluentvalidation.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 1, UrlCode = "TwUR", OriginalUrl="https://facebook.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 2, UrlCode = "TkUR", OriginalUrl="https://youtube.com/", IpAddress="127.0.0.1", PostedDate = DateTime.Now}
            }.AsQueryable();
            var mockSet = new Mock<DbSet<Url>>();
            mockSet.As<IQueryable<Url>>().Setup(m => m.Provider).Returns(arr.Provider);
            mockSet.As<IQueryable<Url>>().Setup(m => m.Expression).Returns(arr.Expression);
            mockSet.As<IQueryable<Url>>().Setup(m => m.ElementType).Returns(arr.ElementType);
            mockSet.As<IQueryable<Url>>().Setup(m => m.GetEnumerator()).Returns(arr.GetEnumerator()); 
            var fakeContext = new Mock<EFDbContext>();
            fakeContext.Setup(ctx => ctx.Urls).Returns(mockSet.Object);
            var validator = new  UrlValidation();
            validator.context = fakeContext.Object;

            Assert.IsTrue(validator.notExist("https://www.youtube.com/"));
            Assert.IsFalse(validator.notExist("https://www.facebook.com/"));
        }

我明白这个系统。在fakeContext.Setup(ctx => ctx.Urls).Returns(mockSet.Object);上抛出的非虚拟错误上的无效设置。我怀疑我的EFDContext不能被覆盖,如下面的用户解释。我不知道如何解决这个问题。我的EFDContext类在上下文中声明如下:

namespace UrlShortener.Domain.Concrete
{
    public class EFDbContext:DbContext
    {
        public DbSet<Url> Urls { get; set; }
    }
}

然后我将EFDContext的属性更改为virtual,这里

namespace UrlShortener.Domain.Concrete
{
    public class EFDbContext:DbContext
    {
        public virtual DbSet<Url> Urls { get; set; }
    }
}

我的测试现在抛出系统。TargetInvocationException与一个内部异常"Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."}。消息The type initializer for 'Castle.Proxies.DbSet 1Proxy抛出异常。'在

处抛出错误

fakeContext.Setup(ctx => ctx.Urls).Returns(mockSet.Object);

请我如何单元测试这个方法

通常,您可以通过使用某种形式的依赖注入(作为构造函数参数,使用公共setter,使用服务定位器,…)将dbcontext或任何其他昂贵/难以测试的依赖注入到被测类中。

这样你就可以为单元测试注入一个假的实现,这个实现是用假的数据和/或对方法调用的假期望来存根的。

你可以在你的EFDbContext类周围包装一个瘦头接口来允许单元测试,或者你可以在真正的EFDbContext上设置一些虚的属性,就像这里解释的那样。

public void Should_know_if_Url_Exist_or_Not()
        { 
            ArrayList arr = new ArrayList();
            arr.AddRange(new Url[]
            {
                new Url{ UrlId = 0, UrlCode = "TYUR", OriginalUrl="https://fluentvalidation.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 1, UrlCode = "TwUR", OriginalUrl="https://facebook.com", IpAddress="127.0.0.1", PostedDate = DateTime.Now},
                new Url{ UrlId = 2, UrlCode = "TkUR", OriginalUrl="https://youtube.com/", IpAddress="127.0.0.1", PostedDate = DateTime.Now}
            });
            var fakeContext = new Mock<EFDbContext>();
            fakeContext
               .Setup(ctx => ctx.Urls)
               .Returns(arr);
            var validator = new UrlValidation(fakeContext.Object);
            Assert.IsTrue(val.notExist("https://www.youtube.com/"));
            Assert.IsFalse(val.notExist("https://www.facebook.com/"));
        }