.नेट कोर यूनिट टेस्टिंग - मॉक आईओशन्स <टी>


137

मुझे ऐसा लग रहा है कि मुझे यहाँ कुछ स्पष्ट याद आ रहा है। मेरे पास ऐसी कक्षाएं हैं जिन्हें .net Core IOptions पैटर्न (?) का उपयोग करके विकल्पों को इंजेक्ट करने की आवश्यकता होती है। जब मैं इकाई परीक्षण के लिए जाता हूं तो मैं कक्षा के विभिन्न संस्करणों का मजाक बनाना चाहता हूं ताकि कक्षा की कार्यक्षमता को मान्य किया जा सके। क्या किसी को पता है कि स्टार्टअप क्लास के बाहर IOptions को सही तरीके से कैसे मॉक / इंस्टेंट / पॉप्युलेट किया जाए?

यहां उन कक्षाओं के कुछ नमूने दिए गए हैं जिनके साथ मैं काम कर रहा हूं:

सेटिंग्स / विकल्प मॉडल

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; }
    }
}

परीक्षण करने के लिए वर्ग जो सेटिंग्स का उपयोग करता है:

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()
        {
        }
    }
}

अन्य वर्गों से अलग विधानसभा में इकाई परीक्षण:

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
क्या आप उस ब्लॉक का एक छोटा कोड उदाहरण प्रदान कर सकते हैं जिसे आप मॉक करने की कोशिश कर रहे हैं? धन्यवाद!
एजे एक्स

क्या आप मॉकिंग का अर्थ भ्रमित कर रहे हैं? आप एक इंटरफ़ेस पर मज़ाक करते हैं और एक निर्दिष्ट मान वापस करने के लिए इसे कॉन्फ़िगर करते हैं। के लिए IOptions<T>आप केवल नकली करने के लिए है Valueवर्ग आप इच्छा वापस जाने के लिए
त्सेंग

जवाबों:


253

आपको किसी IOptions<SampleOptions>ऑब्जेक्ट को मैन्युअल रूप से बनाने और पॉप्युलेट करने की आवश्यकता है । आप Microsoft.Extensions.Options.Optionsसहायक वर्ग के माध्यम से ऐसा कर सकते हैं । उदाहरण के लिए:

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

आप इसे थोड़ा सरल कर सकते हैं:

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

जाहिर है कि यह बहुत उपयोगी नहीं है। आपको वास्तव में एक नमूनाऑब्जेक्ट्स बनाने और पॉप्युलेट करने की आवश्यकता होगी और इसे बनाएं विधि में पास करें।


मैं सभी अतिरिक्त उत्तरों की सराहना करता हूं जो बताते हैं कि Moq का उपयोग कैसे करें, आदि, लेकिन यह उत्तर इतना सरल है कि यह निश्चित रूप से वह है जिसका मैं उपयोग कर रहा हूं। और यह बहुत अच्छा काम करता है!
गृहमद

बहुत बढ़िया जवाब। बहुत आसान है जो एक नकली रूपरेखा पर निर्भर है।
क्रिस लॉरेंस

2
धन्यवाद। मैं new OptionsWrapper<SampleOptions>(new SampleOptions());हर जगह लिखने से इतना थक गया था
BritishDeveloper

59

यदि आप टिप्पणी में @TSeng द्वारा बताए अनुसार मॉकिंग फ्रेमवर्क का उपयोग करने का इरादा रखते हैं, तो आपको अपनी परियोजना में निम्नलिखित निर्भरता जोड़ने की जरूरत है। फ़ाइल फ़ाइल।

   "Moq": "4.6.38-alpha",

एक बार जब निर्भरता बहाल हो जाती है, तो Moq फ्रेमवर्क का उपयोग करना सैंपलओसेस वर्ग का एक उदाहरण बनाने के रूप में सरल है और फिर जैसा कि उल्लेख किया गया है, मान पर असाइन करें।

यहाँ एक कोड रूपरेखा है कि यह कैसा दिखेगा।

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);

एक बार मॉक सेटअप होने के बाद, आप अब मॉक ऑब्जेक्ट को कंस्ट्रक्टर के रूप में पास कर सकते हैं

SampleRepo sr = new SampleRepo(mock.Object);   

HTH।

FYI करें मेरे पास एक git रिपॉजिटरी है जो इन 2 दृष्टिकोणों को Github / patvin80 पर बताता है


यह स्वीकृत उत्तर होना चाहिए, यह पूरी तरह से काम करता है।
एलेसैंड्रोक

वास्तव में यह मेरे लिए काम करना चाहते हैं, लेकिन यह नहीं है :( Moq 4.13.1
kanpeki

21

आप सभी पर MOQ का उपयोग करने से बच सकते हैं। अपने परीक्षण .json कॉन्फ़िगरेशन फ़ाइल में उपयोग करें। कई परीक्षण वर्ग फ़ाइलों के लिए एक फ़ाइल। ConfigurationBuilderइस मामले में उपयोग करना ठीक रहेगा ।

Appsetting.json का उदाहरण

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

मैपिंग क्लास की सेटिंग का उदाहरण:

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

परीक्षण करने के लिए आवश्यक सेवा का उदाहरण:

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

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);
    }
}

यह मेरे लिए अच्छा काम किया, चीयर्स! Moq का उपयोग उस चीज़ के लिए नहीं करना चाहता था जो इतनी सरल थी और कॉन्फ़िगरेशन सेटिंग्स के साथ मेरे अपने विकल्पों को आबाद करने की कोशिश नहीं करना चाहती थी।
हैरी

3
महान काम करता है, लेकिन महत्वपूर्ण जानकारी का गायब होना यह है कि आपको Microsoft.Extensions.Configuration.Binder nuget पैकेज शामिल करना होगा, अन्यथा आपको "Get <SomeServiceConfiguration>" एक्सटेंशन विधि उपलब्ध नहीं है।
काइनेटिक

मुझे यह काम करने के लिए डॉटनेट ऐड पैकेज Microsoft.Extensions.Configuration.Json चलाना पड़ा। बहुत बढ़िया जवाब!
लियोनार्डो वाइल्ड

1
मुझे बिन फ़ाइल का उपयोग करने के लिए appsettings.json फ़ाइल के गुणों को भी बदलना पड़ा, क्योंकि Directory.GetCurrentDirectory () बिन फ़ाइल की सामग्री लौटा रही थी। Appsettings.json की "कॉपी टू आउटपुट डायरेक्टरी" में, मैंने "यदि नया है तो कॉपी करें" का मान सेट किया है।
bpz

14

दिया गया वर्ग Personजो PersonSettingsनिम्नानुसार निर्भर करता है:

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>इसका मजाक उड़ाया Personजा सकता है और निम्नानुसार परीक्षण किया जा सकता है:

[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");    
    }
}

Ctor IOptions<PersonSettings>में Personस्पष्ट रूप से पास करने के बजाय इंजेक्ट करने के लिए , इस कोड का उपयोग करें:

[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");
    }
}

आप उपयोगी कुछ भी परीक्षण नहीं कर रहे हैं। DI मेरे Microsoft के लिए रूपरेखा पहले से ही परीक्षण की गई इकाई है। जैसा कि यह खड़ा है यह वास्तव में एक एकीकरण परीक्षण है (एक 3 पार्टी ढांचे के साथ एकीकरण)।
एरिक फिलिप्स

2
@ErikPhilips मेरा कोड दिखाता है कि ओपी के अनुरोध के अनुसार IOptions <T> को कैसे मॉक करें। मैं इस बात से सहमत हूं कि यह अपने आप में कुछ भी उपयोगी नहीं है, लेकिन यह कुछ और उपयोगी परीक्षण हो सकता है।
फ्रैंक रेम

13

आप हमेशा Options.Create () के माध्यम से अपने विकल्प बना सकते हैं और वास्तव में आपके द्वारा परीक्षण किए जा रहे भंडार का नकली उदाहरण बनाने से पहले AutoMocker.Use (विकल्प) का उपयोग कर सकते हैं। AutoMocker.CreateInstance <> () का उपयोग करना मैन्युअल रूप से गुजरने वाले मापदंडों के बिना उदाहरणों को बनाना आसान बनाता है

मैंने बदला है कि आप नमूनाप्रोपो को थोड़ा सा व्यवहार कर सकते हैं ताकि मुझे लगता है कि आप जो व्यवहार प्राप्त करना चाहते हैं उसे पुन: पेश करने में सक्षम हो।

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

यहां एक और आसान तरीका है जिसमें मॉक की आवश्यकता नहीं है, लेकिन इसके बजाय 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

मेरे सिस्टम और एकीकरण परीक्षणों के लिए, मैं परीक्षण प्रोजेक्ट के अंदर अपनी कॉन्फ़िग फ़ाइल की एक प्रति / लिंक करना पसंद करता हूं। और फिर मैं विकल्प प्राप्त करने के लिए कॉन्फ़िगरेशनब्यूब्रल का उपयोग करता हूं।

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();
        }
    }
}
}

इस तरह मैं अपने टेस्टप्रोजेक्ट के अंदर हर जगह कॉन्फिग का उपयोग कर सकता हूं। यूनिट परीक्षणों के लिए, मैं वर्णित patvin80 जैसे MOQ का उपयोग करना पसंद करता हूं।


1

अलेहा से सहमत हूं कि testSettings.json कॉन्फ़िगरेशन फ़ाइल का उपयोग करना बेहतर है। और फिर IOption को इंजेक्ट करने के बजाय आप बस अपने क्लास कंस्ट्रक्टर में असली सैंपल ओकेजन को इंजेक्ट कर सकते हैं, जब यूनिट क्लास का परीक्षण करती है, तो आप निम्नलिखित को एक फिक्सेटर में या फिर टेस्ट क्लास कंस्ट्रक्टर में कर सकते हैं:

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

  var configurationRoot = builder.Build();
  configurationRoot.GetSection("SampleRepo").Bind(_sampleRepo);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.