Kiểm tra đơn vị lõi .Net - Giả định giả định <T>


137

Tôi cảm thấy như tôi đang thiếu một cái gì đó thực sự rõ ràng ở đây. Tôi có các lớp yêu cầu tiêm các tùy chọn bằng cách sử dụng mẫu .Net Core IOptions (?). Khi tôi đi kiểm tra đơn vị lớp đó, tôi muốn mô phỏng các phiên bản khác nhau của các tùy chọn để xác thực chức năng của lớp. Có ai biết làm thế nào để mô phỏng / khởi tạo / điền chính xác các IOptions bên ngoài lớp Startup không?

Dưới đây là một số mẫu của các lớp tôi đang làm việc với:

Cài đặt / Mô hình tùy chọn

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace OptionsSample.Models
{
    public class SampleOptions
    {
        public string FirstSetting { get; set; }
        public int SecondSetting { get; set; }
    }
}

Lớp được kiểm tra sử dụng Cài đặt:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OptionsSample.Models
using System.Net.Http;
using Microsoft.Extensions.Options;
using System.IO;
using Microsoft.AspNetCore.Http;
using System.Xml.Linq;
using Newtonsoft.Json;
using System.Dynamic;
using Microsoft.Extensions.Logging;

namespace OptionsSample.Repositories
{
    public class SampleRepo : ISampleRepo
    {
        private SampleOptions _options;
        private ILogger<AzureStorageQueuePassthru> _logger;

        public SampleRepo(IOptions<SampleOptions> options)
        {
            _options = options.Value;
        }

        public async Task Get()
        {
        }
    }
}

Kiểm tra đơn vị trong một hội đồng khác với các lớp khác:

using OptionsSample.Repositories;
using OptionsSample.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

namespace OptionsSample.Repositories.Tests
{
    public class SampleRepoTests
    {
        private IOptions<SampleOptions> _options;
        private SampleRepo _sampleRepo;


        public SampleRepoTests()
        {
            //Not sure how to populate IOptions<SampleOptions> here
            _options = options;

            _sampleRepo = new SampleRepo(_options);
        }
    }
}

1
Bạn có thể cung cấp một ví dụ mã nhỏ của khối bạn đang cố gắng giả không? cảm ơn!
AJ X.

Bạn đang nhầm lẫn ý nghĩa của chế giễu? Bạn giả định trên một giao diện và cấu hình nó để trả về một giá trị được chỉ định. Đối với IOptions<T>bạn chỉ phải chế giễu Valueđể trở lại lớp học mà bạn mong muốn
Tseng

Câu trả lời:


253

Bạn cần phải tự tạo và điền vào một IOptions<SampleOptions>đối tượng. Bạn có thể làm như vậy thông qua Microsoft.Extensions.Options.Optionslớp người trợ giúp. Ví dụ:

IOptions<SampleOptions> someOptions = Options.Create<SampleOptions>(new SampleOptions());

Bạn có thể đơn giản hóa một chút để:

var someOptions = Options.Create(new SampleOptions());

Rõ ràng điều này không hữu ích lắm. Bạn sẽ thực sự cần phải tạo và điền vào một đối tượng SampleOptions và chuyển nó vào phương thức Tạo.


Tôi đánh giá cao tất cả các câu trả lời bổ sung cho thấy cách sử dụng Moq, v.v., nhưng câu trả lời này đơn giản đến mức nó chắc chắn là câu tôi đang sử dụng. Và nó hoạt động rất tốt!
grahamesd

Câu trả lời chính xác. Đơn giản hơn nhiều mà dựa vào một khuôn khổ chế giễu.
Chris Lawrence

2
cảm ơn. Tôi đã quá mệt mỏi với việc viết new OptionsWrapper<SampleOptions>(new SampleOptions());ở khắp mọi nơi
Anh

59

Nếu bạn có ý định sử dụng Mocking Framework như được chỉ định bởi @TSeng trong bình luận, bạn cần thêm phụ thuộc sau vào tệp project.json của mình.

   "Moq": "4.6.38-alpha",

Khi sự phụ thuộc được khôi phục, sử dụng khung Moq đơn giản như việc tạo một thể hiện của lớp SampleOptions và sau đó như đã đề cập, gán nó cho Giá trị.

Đây là một phác thảo mã trông như thế nào.

SampleOptions app = new SampleOptions(){Title="New Website Title Mocked"}; // Sample property
// Make sure you include using Moq;
var mock = new Mock<IOptions<SampleOptions>>();
// We need to set the Value of IOptions to be the SampleOptions Class
mock.Setup(ap => ap.Value).Returns(app);

Sau khi thiết lập giả, bây giờ bạn có thể chuyển đối tượng giả cho đối tượng là

SampleRepo sr = new SampleRepo(mock.Object);   

HTH.

FYI Tôi có một kho git phác thảo 2 cách tiếp cận này trên Github / patvin80


Đây phải là câu trả lời được chấp nhận, nó hoạt động hoàn hảo.
alessandrocb

Thực sự mong muốn điều này có hiệu quả với tôi, nhưng nó không :( Moq 4.13.1
kanpeki

21

Bạn có thể tránh sử dụng moq cả. Sử dụng trong tập tin cấu hình .json thử nghiệm của bạn. Một tệp cho nhiều tệp lớp kiểm tra. Nó sẽ ổn để sử dụngConfigurationBuilder trong trường hợp này.

Ví dụ về appsetting.json

{
    "someService" {
        "someProp": "someValue
    }
}

Ví dụ về lớp ánh xạ cài đặt:

public class SomeServiceConfiguration
{
     public string SomeProp { get; set; }
}

Ví dụ về dịch vụ cần thiết để kiểm tra:

public class SomeService
{
    public SomeService(IOptions<SomeServiceConfiguration> config)
    {
        _config = config ?? throw new ArgumentNullException(nameof(_config));
    }
}

Lớp kiểm tra NUnit:

[TestFixture]
public class SomeServiceTests
{

    private IOptions<SomeServiceConfiguration> _config;
    private SomeService _service;

    [OneTimeSetUp]
    public void GlobalPrepare()
    {
         var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", false)
            .Build();

        _config = Options.Create(configuration.GetSection("someService").Get<SomeServiceConfiguration>());
    }

    [SetUp]
    public void PerTestPrepare()
    {
        _service = new SomeService(_config);
    }
}

Điều này làm việc tốt cho tôi, chúc mừng! Không muốn sử dụng Moq cho một thứ gì đó có vẻ đơn giản và không muốn thử điền các tùy chọn của riêng tôi với cài đặt cấu hình.
Harry

3
Hoạt động rất tốt, nhưng phần thiếu thông tin quan trọng là bạn cần bao gồm gói nuget Microsoft.Extensions.Configuration.Binder, nếu không, bạn không có sẵn phương thức tiện ích mở rộng "Get <someServiceConfiguration>".
Kinetic

Tôi đã phải chạy dotnet thêm gói Microsoft.Extensions.Configuration.Json để làm việc này. Câu trả lời chính xác!
Leonardo Wildt

1
Tôi cũng đã phải thay đổi các thuộc tính của tệp appsinstall.json để sử dụng tệp trong tệp bin, vì Directory.GetCienDirectory () đang trả về nội dung của tệp bin. Trong "Sao chép vào thư mục đầu ra" của appsinstall.json, tôi đặt giá trị thành "Sao chép nếu mới hơn".
bpz

14

Cho lớp Personphụ thuộc vào PersonSettingsnhư sau:

public class PersonSettings
{
    public string Name;
}

public class Person
{
    PersonSettings _settings;

    public Person(IOptions<PersonSettings> settings)
    {
        _settings = settings.Value;
    }

    public string Name => _settings.Name;
}

IOptions<PersonSettings>có thể bị chế giễu và Personcó thể được kiểm tra như sau:

[TestFixture]
public class Test
{
    ServiceProvider _provider;

    [OneTimeSetUp]
    public void Setup()
    {
        var services = new ServiceCollection();
        // mock PersonSettings
        services.AddTransient<IOptions<PersonSettings>>(
            provider => Options.Create<PersonSettings>(new PersonSettings
            {
                Name = "Matt"
            }));
        _provider = services.BuildServiceProvider();
    }

    [Test]
    public void TestName()
    {
        IOptions<PersonSettings> options = _provider.GetService<IOptions<PersonSettings>>();
        Assert.IsNotNull(options, "options could not be created");

        Person person = new Person(options);
        Assert.IsTrue(person.Name == "Matt", "person is not Matt");    
    }
}

Để tiêm IOptions<PersonSettings>vào Personthay vì chuyển nó một cách rõ ràng đến ctor, hãy sử dụng mã này:

[TestFixture]
public class Test
{
    ServiceProvider _provider;

    [OneTimeSetUp]
    public void Setup()
    {
        var services = new ServiceCollection();
        services.AddTransient<IOptions<PersonSettings>>(
            provider => Options.Create<PersonSettings>(new PersonSettings
            {
                Name = "Matt"
            }));
        services.AddTransient<Person>();
        _provider = services.BuildServiceProvider();
    }

    [Test]
    public void TestName()
    {
        Person person = _provider.GetService<Person>();
        Assert.IsNotNull(person, "person could not be created");

        Assert.IsTrue(person.Name == "Matt", "person is not Matt");
    }
}

Bạn không kiểm tra bất cứ điều gì hữu ích. Khung cho DI Microsoft của tôi đã được thử nghiệm. Vì nó thực sự là một thử nghiệm tích hợp (tích hợp với khung bên thứ 3).
Erik Philips

2
@ErikPhilips Mã của tôi hiển thị cách giả lập IOptions <T> theo yêu cầu của OP. Tôi đồng ý rằng bản thân nó không kiểm tra bất cứ điều gì hữu ích, nhưng nó có thể hữu ích để kiểm tra một cái gì đó khác.
Frank Rem

13

Bạn luôn có thể tạo các tùy chọn của mình thông qua Options.Create () và đơn giản hơn là sử dụng AutoMocker.Use (tùy chọn) trước khi thực sự tạo ra phiên bản giả của kho lưu trữ mà bạn đang kiểm tra. Sử dụng AutoMocker.CreateInstance <> () giúp tạo các cá thể dễ dàng hơn mà không cần truyền tham số thủ công

Tôi đã thay đổi Mẫu của bạn một chút để có thể tái tạo hành vi mà tôi nghĩ bạn muốn đạt được.

public class SampleRepoTests
{
    private readonly AutoMocker _mocker = new AutoMocker();
    private readonly ISampleRepo _sampleRepo;

    private readonly IOptions<SampleOptions> _options = Options.Create(new SampleOptions()
        {FirstSetting = "firstSetting"});

    public SampleRepoTests()
    {
        _mocker.Use(_options);
        _sampleRepo = _mocker.CreateInstance<SampleRepo>();
    }

    [Fact]
    public void Test_Options_Injected()
    {
        var firstSetting = _sampleRepo.GetFirstSetting();
        Assert.True(firstSetting == "firstSetting");
    }
}

public class SampleRepo : ISampleRepo
{
    private SampleOptions _options;

    public SampleRepo(IOptions<SampleOptions> options)
    {
        _options = options.Value;
    }

    public string GetFirstSetting()
    {
        return _options.FirstSetting;
    }
}

public interface ISampleRepo
{
    string GetFirstSetting();
}

public class SampleOptions
{
    public string FirstSetting { get; set; }
}

8

Đây là một cách dễ dàng khác không cần Mock, nhưng thay vào đó sử dụng OptionsWrapper:

var myAppSettingsOptions = new MyAppSettingsOptions();
appSettingsOptions.MyObjects = new MyObject[]{new MyObject(){MyProp1 = "one", MyProp2 = "two", }};
var optionsWrapper = new OptionsWrapper<MyAppSettingsOptions>(myAppSettingsOptions );
var myClassToTest = new MyClassToTest(optionsWrapper);

2

Đối với các thử nghiệm tích hợp và hệ thống của tôi, tôi muốn có một bản sao / liên kết của tệp cấu hình của mình bên trong dự án thử nghiệm. Và sau đó tôi sử dụng Trình cấu hình để có được các tùy chọn.

using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace SomeProject.Test
{
public static class TestEnvironment
{
    private static object configLock = new object();

    public static ServiceProvider ServiceProvider { get; private set; }
    public static T GetOption<T>()
    {
        lock (configLock)
        {
            if (ServiceProvider != null) return (T)ServiceProvider.GetServices(typeof(T)).First();

            var builder = new ConfigurationBuilder()
                .AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: true)
                .AddEnvironmentVariables();
            var configuration = builder.Build();
            var services = new ServiceCollection();
            services.AddOptions();

            services.Configure<ProductOptions>(configuration.GetSection("Products"));
            services.Configure<MonitoringOptions>(configuration.GetSection("Monitoring"));
            services.Configure<WcfServiceOptions>(configuration.GetSection("Services"));
            ServiceProvider = services.BuildServiceProvider();
            return (T)ServiceProvider.GetServices(typeof(T)).First();
        }
    }
}
}

Bằng cách này, tôi có thể sử dụng cấu hình ở mọi nơi trong TestProject của mình. Đối với các bài kiểm tra đơn vị, tôi thích sử dụng Moq như patvin80 được mô tả.


1

Đồng ý với Aleha rằng sử dụng tệp cấu hình testSinstall.json có lẽ tốt hơn. Và sau đó, thay vì tiêm IOption, bạn có thể chỉ cần tiêm SampleOptions thực trong trình xây dựng lớp của mình, khi đơn vị kiểm tra lớp, bạn có thể thực hiện các thao tác sau trong một vật cố định hoặc một lần nữa chỉ trong hàm tạo của lớp kiểm tra:

   var builder = new ConfigurationBuilder()
  .AddJsonFile("testSettings.json", true, true)
  .AddEnvironmentVariables();

  var configurationRoot = builder.Build();
  configurationRoot.GetSection("SampleRepo").Bind(_sampleRepo);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.