moq로 ConfigurationManager.AppSettings를 모의하는 방법


123

나는 조롱하는 방법을 모르는이 코드 지점에 갇혀 있습니다.

ConfigurationManager.AppSettings["User"];

ConfigurationManager를 조롱해야하지만 단서가 없습니다 . Moq를 사용하고 있습니다.

누군가 나에게 팁을 줄 수 있습니까? 감사!

답변:


103

이에 대한 한 가지 표준 접근 방식 은 구성 관리자를 래핑 하기 위해 파사드 패턴 을 사용하고 느슨하게 결합되어 제어 할 수있는 것입니다.

따라서 ConfigurationManager를 래핑합니다. 다음과 같은 것 :

public class Configuration: IConfiguration
{
    public User
    {
        get
        { 
            return ConfigurationManager.AppSettings["User"];
        }
    }
}

(구성 클래스에서 인터페이스를 추출한 다음 코드의 모든 곳에서 해당 인터페이스를 사용할 수 있습니다.) 그런 다음 IConfiguration을 모의합니다. 몇 가지 다른 방법으로 파사드 자체를 구현할 수 있습니다. 위에서 저는 개별 속성을 래핑하기로 선택했습니다. 또한 약한 형식의 해시 배열이 아닌 강력한 형식의 정보를 사용하는 부수적 인 이점도 얻을 수 있습니다.


6
이것은 개념적으로도 제가하고있는 일입니다. 그러나 나는 즉시 인터페이스 구현을 생성하는 Castle DictionaryAdapter ( Castle Core의 일부 )를 사용합니다. 나는 얼마 전에 그것에 대해 썼다 : blog.andreloker.de/post/2008/09/05/… (내가 Castle DictionaryAdapter를 어떻게 사용하는지 보려면 "A Solution"으로 스크롤)
Andre Loker

그것은 멋지고 좋은 기사입니다. 나는 미래를 위해 이것을 명심해야 할 것입니다.
Joshua Enfield

나는 또한 당신의 순수성과 해석에 따라 추가 할 수 있습니다. 이것은 대신 위임 프록시 또는 어댑터라고 불릴 수 있습니다.
Joshua Enfield

3
위에서부터 Moq를 "보통"으로 사용하고 있습니다. 테스트되지 않은,하지만 같은 : var configurationMock = new Mock<IConfiguration>();및 설정을위한 :configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
여호수아 엔필드

이 시나리오는 계층이 IConfiguration에 종속되어 있고 IConfiguration을 모의해야 할 때 사용되지만 IConfiguration 구현을 어떻게 테스트합니까? 그리고 단위 테스트 ConfigurationManager.AppSettings [ "User"]를 호출하면 단위를 테스트하지 않고 단위 테스트가 아닌 구성 파일에서 가져온 값을 테스트합니다. 당신은 구현을 확인해야하는 경우, 참조 @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov

173

AspnetMvc4를 사용하고 있습니다. 조금 전에 나는 썼다

ConfigurationManager.AppSettings["mykey"] = "myvalue";

내 테스트 방법에서 완벽하게 작동했습니다.

설명 : 테스트 메소드는 일반적으로 web.config또는 에서 가져온 앱 설정이있는 컨텍스트에서 실행됩니다 myapp.config. ConfigurationsManager이 응용 프로그램 전역 개체에 도달하여 조작 할 수 있습니다.

하지만 : 테스트를 병렬로 실행하는 테스트 러너가있는 경우 이것은 좋은 생각이 아닙니다.


8
이것은 문제를 해결하는 정말 영리하고 간단한 방법입니다! 단순성에 대한 찬사!
Navap 2016 년

1
대부분의 경우 추상화를 만드는 것보다 훨씬 쉽습니다
Michael Clark

2
그게 다야 ???? 이 특별한 봉인 된 클래스를 테스트하는 방법에 대해 머리를 찌르고 있었기 때문에 그 탁월함은 단순함에 있습니다.
Piotr Kula

5
ConfigurationManager.AppSettingsNameValueCollection스레드로부터 안전하지 않으므로 적절한 동기화없이 병렬 테스트를 사용하는 것은 어쨌든 좋은 생각이 아닙니다. 그렇지 않으면 당신은 ConfigurationManager.AppSettings.Clear()당신의 TestInitialize/ ctor를 부르면 당신은 황금색입니다.
Ohad Schneider

1
간단하고 간결합니다. 단연 최고의 답변입니다!
znn

21

수행해야 할 작업이 아닐 수도 있지만 테스트 프로젝트에서 app.config를 사용하는 것을 고려 했습니까? 따라서 ConfigurationManager는 app.config에 입력 한 값을 가져 오므로 아무것도 모의 할 필요가 없습니다. 이 솔루션은 "변수"구성 파일을 테스트 할 필요가 없기 때문에 내 요구에 적합합니다.


7
테스트중인 코드의 동작이 구성 값의 값에 따라 변경되는 경우 AppSettings에 직접 의존하지 않는 경우 테스트하는 것이 더 쉽습니다.
Andre

2
다른 가능한 설정을 테스트하지 않기 때문에 이것은 나쁜 습관입니다. Joshua Enfield의 답변은 테스트에 적합합니다.
mkaj

4
다른 사람들은이 대답에 반대하지만 그들의 입장은 약간 일반화되었다고 말하고 싶습니다. 이것은 일부 시나리오에서 매우 유효한 대답이며 실제로 필요한 것이 무엇인지에 달려 있습니다. 예를 들어, 각각 다른 기본 URL을 가진 4 개의 다른 클러스터가 있다고 가정 해 보겠습니다. 이러한 4 개의 클러스터는 런타임 중에 Web.config프로젝트를 포함하는 항목 에서 가져옵니다 . 테스트 중에에서 잘 알려진 값을 가져 오는 app.config것은 매우 유효합니다. 단위 테스트는 "cluster1"이 작동한다고 풀 때 조건이 작동하는지 확인하기 만하면됩니다. 이 경우에는 4 개의 다른 클러스터 만 있습니다.
Mike Perrenoud 2015

14

shim을 사용 AppSettings하여 사용자 지정 NameValueCollection개체 를 수정할 수 있습니다 . 다음은이를 달성 할 수있는 방법의 예입니다.

[TestMethod]
public void TestSomething()
{
    using(ShimsContext.Create()) {
        const string key = "key";
        const string value = "value";
        ShimConfigurationManager.AppSettingsGet = () =>
        {
            NameValueCollection nameValueCollection = new NameValueCollection();
            nameValueCollection.Add(key, value);
            return nameValueCollection;
        };

        ///
        // Test code here.
        ///

        // Validation code goes here.        
    }
}

shim 및 fake에 대한 자세한 내용은 Isolating Code Under Test with Microsoft Fakes에서 확인할 수 있습니다. 도움이 되었기를 바랍니다.


6
저자는 MS Fakes가 아니라 moq를 사용하는 방법을 요구하고 있습니다.
JPCF

6
그리고 이것은 어떻게 다른가요? 그의 코드에서 데이터 종속성을 제거하여 조롱을 달성합니다. C # Fakes를 사용하는 것이 한 가지 방법입니다!
Zorayr

9

조롱하는 대신 스터 빙을 고려해 보셨습니까? AppSettings속성은이다 NameValueCollection:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Arrange
        var settings = new NameValueCollection {{"User", "Otuyh"}};
        var classUnderTest = new ClassUnderTest(settings);

        // Act
        classUnderTest.MethodUnderTest();

        // Assert something...
    }
}

public class ClassUnderTest
{
    private readonly NameValueCollection _settings;

    public ClassUnderTest(NameValueCollection settings)
    {
        _settings = settings;
    }

    public void MethodUnderTest()
    {
        // get the User from Settings
        string user = _settings["User"];

        // log
        Trace.TraceInformation("User = \"{0}\"", user);

        // do something else...
    }
}

이점은 구현이 간단하고 실제로 필요할 때까지 System.Configuration에 대한 종속성이 없다는 것입니다.


3
이 접근 방식이 가장 좋습니다. 한편 IConfiguration으로 Joshua Enfield가 제안한대로 구성 관리자를 래핑하는 것은 너무 높은 수준 일 수 있으며 잘못된 구성 값 구문 분석 등으로 ​​인해 존재하는 버그를 놓칠 수 있습니다. 반면에 ConfigurationManager.AppSettingsLosManos가 제안한대로 직접 사용 하는 것은 구현 세부 사항이 너무 많으며 다른 테스트에 부작용이있을 수 있으며 수동 동기화없이 병렬 테스트 실행에 사용할 수 없습니다 ( NameValueConnection스레드 안전하지 않음).
Ohad Schneider

2

이것은 정적 속성이며 Moq는 상속을 통해 모의 될 수있는 Moq 인스턴스 메서드 또는 클래스로 설계되었습니다. 다시 말해, Moq는 여기서 당신에게 어떤 도움도되지 않을 것입니다.

모의 통계를 위해 무료 인 Moles 라는 도구를 사용합니다 . 유료 도구라고 생각하지만 Typemock과 같은 다른 프레임 워크 격리 도구도 있습니다.

정적 및 테스트와 관련하여 또 다른 옵션은 정적 상태를 직접 만드는 것입니다.하지만 이것은 종종 문제가 될 수 있습니다 (귀하의 경우에 해당한다고 생각합니다).

그리고 마지막으로 격리 프레임 워크가 선택 사항이 아니고이 접근 방식에 전념한다면 Joshua가 언급 한 파사드가 좋은 접근 방식이거나 일반적으로 클라이언트 코드를 비즈니스 논리에서 제외하는 모든 접근 방식입니다. 테스트에 사용하고 있습니다.


1

자신의 app.config 공급자를 작성하는 것은 간단한 작업이며 다른 것보다 더 유용하다고 생각합니다. 특히 shim 등과 같은 가짜는 피해야합니다. 사용하자마자 Edit & Continue가 더 이상 작동하지 않기 때문입니다.

내가 사용하는 공급자는 다음과 같습니다.

기본적으로 그들은 값을 얻지 App.config만 단위 테스트의 경우 모든 값을 재정의하고 각 테스트에서 독립적으로 사용할 수 있습니다.

인터페이스가 필요하지 않거나 매번 반복해서 구현할 필요가 없습니다. 나는 유틸리티 dll을 가지고 있으며 많은 프로젝트와 단위 테스트 에서이 작은 도우미를 사용합니다.

public class AppConfigProvider
{
    public AppConfigProvider()
    {
        ConnectionStrings = new ConnectionStringsProvider();
        AppSettings = new AppSettingsProvider();
    }

    public ConnectionStringsProvider ConnectionStrings { get; private set; }

    public AppSettingsProvider AppSettings { get; private set; }
}

public class ConnectionStringsProvider
{
    private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public string this[string key]
    {
        get
        {
            string customValue;
            if (_customValues.TryGetValue(key, out customValue))
            {
                return customValue;
            }

            var connectionStringSettings = ConfigurationManager.ConnectionStrings[key];
            return connectionStringSettings == null ? null : connectionStringSettings.ConnectionString;
        }
    }

    public Dictionary<string, string> CustomValues { get { return _customValues; } }
}

public class AppSettingsProvider
{
    private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public string this[string key]
    {
        get
        {
            string customValue;
            return _customValues.TryGetValue(key, out customValue) ? customValue : ConfigurationManager.AppSettings[key];
        }
    }

    public Dictionary<string, string> CustomValues { get { return _customValues; } }
}

1

필요한 것을 설정하는 것은 어떻습니까? .NET을 조롱하고 싶지 않기 때문에 ...?

System.Configuration.ConfigurationManager.AppSettings["myKey"] = "myVal";

앱이 원하는 내용 만 볼 수 있도록 미리 AppSettings를 정리해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.