Cómo burlarse de ConfigurationManager.AppSettings con moq

123

Estoy atascado en este punto del código que no sé cómo burlarme:

ConfigurationManager.AppSettings["User"];

Tengo que burlarme del ConfigurationManager, pero no tengo ni idea, estoy usando Moq .

Alguien me puede dar una propina? ¡Gracias!

Otuyh
fuente

Respuestas:

103

Creo que un enfoque estándar para esto es usar un patrón de fachada para envolver el administrador de configuración y luego tienes algo vagamente acoplado sobre el que tienes control.

Entonces, envolvería el ConfigurationManager. Algo como:

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

(Puede extraer una interfaz de su clase de configuración y luego usar esa interfaz en todas partes en su código) Luego simplemente se burla de la IConfiguration. Es posible que pueda implementar la fachada en sí de diferentes maneras. Arriba elegí solo para envolver las propiedades individuales. También obtiene el beneficio adicional de tener información fuertemente tipada para trabajar en lugar de matrices hash tipadas débilmente.

Joshua Enfield
fuente
66
Esto es conceptualmente lo que estoy haciendo también. Sin embargo, uso Castle DictionaryAdapter (parte de Castle Core) que genera la implementación de la interfaz sobre la marcha. Escribí sobre esto hace algún tiempo: blog.andreloker.de/post/2008/09/05/… (desplácese hacia abajo hasta "A Solution" para ver cómo uso Castle DictionaryAdapter)
Andre Loker
Eso es ingenioso y ese es un buen artículo. Tendré que tener esto en cuenta para el futuro.
Joshua Enfield
También podría agregar, dependiendo de su purismo e interpretaciones, esto podría en cambio o también llamarse Delegado Proxy o Adaptador.
Joshua Enfield
3
Desde arriba solo está usando Moq como "normal". Sin probar, pero algo así como: var configurationMock = new Mock<IConfiguration>();y para la configuración:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Joshua Enfield
Este escenario se usa cuando una capa depende de IConfiguration y necesita burlarse de IConfiguration, pero ¿cómo probaría la implementación de IConfiguration? Y si llama a un ConfigurationManager.AppSettings ["Usuario"] de prueba de unidad, esto no probaría la unidad, pero probaría qué valores se obtienen del archivo de configuración, que no es una prueba de unidad. Si necesita verificar la implementación, consulte @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov el
173

Estoy usando AspnetMvc4. Hace un momento escribí

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

en mi método de prueba y funcionó perfectamente.

Explicación: el método de prueba se ejecuta en un contexto con la configuración de la aplicación tomada, generalmente, una web.configo myapp.config. ConfigurationsManagerpuede alcanzar este objeto global de aplicación y manipularlo.

Sin embargo: si tiene un corredor de prueba que ejecuta pruebas en paralelo, no es una buena idea.

LosManos
fuente
8
¡Esta es una forma realmente inteligente y sencilla de resolver el problema! Felicitaciones por la simplicidad!
Navap
1
Mucho más fácil que crear una abstracción en la mayoría de los casos
Michael Clark
2
¿¿¿¿Eso es???? La brillantez está en la simplicidad cuando estaba investigando cómo evaluar esta clase sellada en particular.
Piotr Kula
55
ConfigurationManager.AppSettingses un sistema NameValueCollectionque no es seguro para subprocesos, por lo que las pruebas paralelas que lo usan sin la sincronización adecuada no son una buena idea de todos modos. De lo contrario, puede llamar ConfigurationManager.AppSettings.Clear()a su TestInitialize/ ctor y estará dorado.
Ohad Schneider
1
Simple y conciso. De lejos, la mejor respuesta!
znn
21

Tal vez no sea lo que necesita lograr, pero ¿ha considerado usar una app.config en su proyecto de prueba? Por lo tanto, ConfigurationManager obtendrá los valores que pones en app.config y no necesitas burlarte de nada. Esta solución funciona bien para mis necesidades, porque nunca necesito probar un archivo de configuración "variable".

Iridio
fuente
77
Si el comportamiento del código bajo prueba cambia dependiendo del valor de un valor de configuración, ciertamente es más fácil probarlo si no depende directamente de AppSettings.
Andre Loker
2
Esta es una mala práctica ya que nunca está probando otras configuraciones posibles. La respuesta de Joshua Enfield es excelente para la prueba.
mkaj
44
Mientras que otros están en contra de esta respuesta, diría que su posición es un poco generalizada. Esta es una respuesta muy válida en algunos escenarios y realmente depende de cuáles sean sus necesidades. Por ejemplo, supongamos que tengo 4 grupos diferentes, cada uno con una URL base diferente. Esos 4 grupos se extraen, durante el tiempo de ejecución, del Web.configproyecto que lo abarca. Durante las pruebas, extraer algunos valores bien conocidos del app.configes muy válido. La prueba de la unidad solo necesita asegurarse de que las condiciones cuando se tira dicen "cluster1" funciona; solo hay 4 grupos diferentes en este caso.
Mike Perrenoud
14

Puede usar cuñas para modificar AppSettingsa un NameValueCollectionobjeto personalizado . Aquí hay un ejemplo de cómo puede lograr esto:

[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.        
    }
}

Puede leer más sobre las laminillas y falsificaciones en Aislar el código bajo prueba con falsificaciones de Microsoft . Espero que esto ayude.

Zorayr
fuente
66
El autor está preguntando cómo hacerlo con moq, no sobre MS Fakes.
JPCF
66
¿Y cómo es esto diferente? Logra burla al eliminar la dependencia de datos de su código. ¡Usar C # Fakes es un enfoque!
Zorayr
9

¿Has considerado tropezar en lugar de burlarte? La AppSettingspropiedad es un 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...
    }
}

Los beneficios son una implementación más simple y sin dependencia del sistema. Configuración hasta que realmente lo necesite.

DanielLarsenNZ
fuente
3
Me gusta este enfoque lo mejor. Por un lado, envolver el administrador de configuración con un, IConfigurationcomo sugiere Joshua Enfield, puede ser un nivel demasiado alto, y es posible que se pierdan errores que existen debido a cosas como un análisis de valor de configuración incorrecto. Por otro lado, usar ConfigurationManager.AppSettingsdirectamente como sugiere LosManos es demasiado detalle de implementación, sin mencionar que puede tener efectos secundarios en otras pruebas y no puede usarse en pruebas paralelas sin sincronización manual (ya NameValueConnectionque no es seguro para subprocesos).
Ohad Schneider
2

Esa es una propiedad estática, y Moq está diseñado para métodos o clases de instancia de Moq que se pueden burlar mediante herencia. En otras palabras, Moq no te será de ninguna ayuda aquí.

Para simular estadísticas, uso una herramienta llamada Moles , que es gratuita. Hay otras herramientas de aislamiento de marcos, como Typemock que también pueden hacer esto, aunque creo que son herramientas pagas.

Cuando se trata de estadísticas y pruebas, otra opción es crear el estado estático usted mismo, aunque esto a menudo puede ser problemático (como, imagino, sería en su caso).

Y, finalmente, si los marcos de aislamiento no son una opción y usted está comprometido con este enfoque, la fachada mencionada por Joshua es un buen enfoque, o cualquier enfoque en general en el que factorice el código de cliente de esto lejos de la lógica comercial que usted estás usando para probar.

Erik Dietrich
fuente
1

Creo que escribir su propio proveedor de app.config es una tarea simple y es más útil que cualquier otra cosa. Especialmente debe evitar cualquier falsificación como cuñas, etc. porque tan pronto como los use Editar y continuar ya no funciona.

Los proveedores que uso se ven así:

De manera predeterminada, obtienen los valores de las App.configpruebas unitarias, pero puedo anular todos los valores y usarlos en cada prueba de forma independiente.

No hay necesidad de ninguna interfaz o implementarla una y otra vez. Tengo un dll de utilidades y uso este pequeño ayudante en muchos proyectos y pruebas unitarias.

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; } }
}
t3chb0t
fuente
1

¿Qué tal simplemente configurar lo que necesita? Porque, no quiero burlarme de .NET, ¿no ...?

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

Probablemente deberías limpiar AppSettings de antemano para asegurarte de que la aplicación solo vea lo que quieres.

Eike
fuente