Не удается прочитать app.config в проекте С#.NET Core unit test с ConfigurationManager

Я создал простой проект модульного теста для чтения файла app.config. Целевой фреймворк - Core 2.0. Я также создал консольное приложение Core 2.0 для проверки работоспособности, чтобы убедиться, что я не делал ничего странного (такой же тест прошел, как и ожидалось, в модульном тестовом проекте.NET 4.6.1).

Консольное приложение хорошо читает app.config, но метод модульного тестирования не работает, и я не могу понять, почему. Оба используют копию одного и того же app.config (не добавлен как ссылка), и оба имеют установленный пакет NuGet System.Configuration.ConfigurationManager v4.4.1.

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="Test1" value ="This is test 1."/>
    <add key="Test2" value ="42"/>
    <add key="Test3" value ="-42"/>
    <add key="Test4" value="true"/>
    <add key="Test5" value="false"/>
    <add key="Test6" value ="101.101"/>
    <add key="Test7" value ="-1.2345"/>
  </appSettings>
</configuration>

Unit тест

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Configuration;

namespace ConfigTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod()]
        public void ConfigTest()
        {
            foreach (string s in ConfigurationManager.AppSettings.AllKeys)
            {
                System.Console.WriteLine(s);
                System.Diagnostics.Debug.WriteLine(s);
            }

            //AllKeys.Length is 0? Should be 7...
            Assert.IsTrue(ConfigurationManager.AppSettings.AllKeys.Length == 7);
        }
    }
}

Консольное приложение

using System;
using System.Configuration;

namespace ConfigTestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (string s in ConfigurationManager.AppSettings.AllKeys)
            {
                Console.WriteLine(s);
                System.Diagnostics.Debug.WriteLine(s);
            }

            //Outputs 7 as expected
            Console.WriteLine(ConfigurationManager.AppSettings.AllKeys.Length);
        }
    }
}  

Учитывая, что я все еще довольно новичок во всем мире.NET Core, я делаю здесь что-то совершенно неправильное? Я просто чувствую себя сумасшедшим в данный момент...

Both projects with an app.config

Ответ 1

Я столкнулся с той же проблемой с моими тестами xunit и решил ее, используя экземпляр Configuration из ConfigurationManager. Я положил статический (нормальный) способ, которым он работает в ядре, фреймворке (но не в модульных тестах), прежде чем показать альтернативный способ, которым он работает во всех трех:

        var appSettingValFromStatic = ConfigurationManager.AppSettings["mySetting"];
        var appSettingValFromInstance = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["mySetting"].Value;

А вот похожая/связанная с этим проблема. Если кому-то понадобится получить раздел, вы можете сделать то же самое, хотя тип должен измениться в конфигурации приложения:

<configSections>
    <section name="customAppSettingsSection" type="System.Configuration.AppSettingsSection"/>
    <section name="customNameValueSectionHandlerSection" type="System.Configuration.NameValueSectionHandler"/>
</configSections>

<customAppSettingsSection>
    <add key="customKey" value="customValue" />
</customAppSettingsSection>

<customNameValueSectionHandlerSection>
    <add key="customKey" value="customValue" />
</customNameValueSectionHandlerSection>

Код для захвата раздела:

        var valFromStatic = ((NameValueCollection)ConfigurationManager.GetSection("customNameValueSectionHandlerSection"))["customKey"];
        var valFromInstance = ((AppSettingsSection)ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).GetSection("customAppSettingsSection")).Settings["customKey"].Value;

Я чувствую, что я тоже сумасшедший, и я знаю, что есть новые способы конфигурирования в ядре, но если кто-то хочет сделать что-то кроссплатформенное, это единственный способ, которым я знаю как. Мне было бы очень интересно, если у кого-нибудь есть альтернативы

Ответ 2

Если вы проверите результат вызова ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

Он должен сказать вам, где должен находиться требуемый файл конфигурации при выполнении модульных тестов для этой сборки.

Я обнаружил, что вместо файла app.config ConfigurationManager ищет файл testhost.dll.config.

Это было для проекта, нацеленного на netcoreapp2.1, со ссылкой на Microsoft.NET.Test.Sdk, NUnit 3.11 и Nunit3TestAdapter 3.12.0

Ответ 3

API ConfigurationManager будет использовать только конфигурацию приложения, которое в настоящее время работает. В проекте unit test это означает app.config тестового проекта, а не консольное приложение.

Приложения .NET Core не должны использовать app.config или ConfigurationManager, так как это устаревшая система конфигурации "полная структура".

Вместо этого используйте Microsoft.Extensions.Configuration для чтения файлов конфигурации JSON, XML или INI. См. Этот документ: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration

Ответ 4

Ни один из ответов, приведенных здесь, не обеспечивает жизнеспособного обходного пути, когда вы имеете дело с кодом, который напрямую обращается к статическим свойствам ConfigurationManager таким как AppSettings или ConnectionStrings.

Правда в том, что это невозможно в данный момент. Вы можете прочитать обсуждение здесь, чтобы понять, почему: https://github.com/dotnet/corefx/issues/22101

Поговорить о том, как реализовать поддержку, можно здесь: https://github.com/Microsoft/vstest/issues/1758.

По моему мнению, имеет смысл поддерживать этот сценарий, поскольку он работает над.NET Framework плюс System.Configuration.ConfigurationManager является библиотекой.NET Standard 2.0.

Ответ 5

Обычно в проектах .NET Framework любой файл App.config был скопирован в папку bin с помощью Visual Studio с именем вашего исполняемого файла (myApp.exe.config), чтобы он мог быть доступен во время выполнения. Больше нет в .NET Standard или Core Framework. Вы должны вручную скопировать и установить файл в папку bin/debug или release. После этого можно получить что-то вроде:

                string AssemblyName = System.IO.Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().GetName().CodeBase);
            AppConfig = (System.Configuration.Configuration)System.Configuration.ConfigurationManager.OpenExeConfiguration(AssemblyName);

Ответ 6

Хакерский, но работающий способ - скопировать конфигурацию в ту же папку, что и сборка записи, какой бы она ни была:

[SetUpFixture]
public class ConfigKludge
{
    [OneTimeSetUp]
    public void Setup() =>
        File.Copy(
            Assembly.GetExecutingAssembly().Location + ".config",
            Assembly.GetEntryAssembly().Location + ".config",
            true);

    [OneTimeTearDown]
    public void Teardown() =>
        File.Delete(Assembly.GetEntryAssembly().Location + ".config");
}

Помимо добавления этого класса, единственное, что заставляет его работать, - это включение файла app.config в тестовый проект (без каких-либо параметров копирования). Он должен быть скопирован в выходную папку как <your test project name>.dll.config на этапе сборки, поскольку это своего рода логика по умолчанию.

Обратите внимание на документацию для OneTimeSetUpAttribute:

Сводка: Определяет метод, который вызывается один раз для выполнения настройки перед выполнением любых дочерних тестов.

Хотя он должен работать для параллельных тестовых прогонов для одного проекта, могут возникнуть очевидные проблемы при запуске двух тестовых проектов одновременно, поскольку конфигурация будет перезаписана.

Тем не менее, он по-прежнему подходит для контейнерных тестовых прогонов, как в Travis.

Ответ 7

Просматривая комментарии к выпуску github, я нашел способ обойти это в файле msbuild...

<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
  <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
</Target>

Это облегчает проверку существующих тестов в .NET Core перед переносом данных конфигурации в файлы конфигурации json.