Can't access information from configuration files when tests have host type "Moles"

2k views Asked by At

We are having problems accessing information in .net configuration files (such as app.config and web.config) via unit tests when the unit tests have a host type of "Moles". It's causing quite a few headaches, so I hope someone has an idea on what can be done.

We're using Visual Studio 2010, and I believe we've tried this on a machine with VS 2010 SP1 installed, and machine without SP1 installed, as well as trying it on 32 bit and 64 bit machines.

I've taken the liberty of reducing the test to its simplest terms. The problem can be recreated by composing a unit testing project consisting of the following two files, and running the test after uncommenting the only commented line. The test works without a host type, but when you introduce Moles as a host type, the null assertion in the test fails. We are not sure why.

First, the configuration file App.config:

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="Connection" connectionString="Something" />
  </connectionStrings>
</configuration>

Next, the test class containing a single test:

namespace TestProject
    {
    using System.Configuration;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class UnitTest
        {

        [TestMethod]
        //[HostType("Moles")]
        public void TestMethod()
            {
            var data = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            Assert.IsNotNull(data.ConnectionStrings.ConnectionStrings["Connection"]);
            }

        }

    }

I would be grateful if anyone could offer any insight.

Thanks very much,

Nick

3

There are 3 answers

0
Gebb On BEST ANSWER

I'm not sure if it will do the job, but you can try this workaround: open the config using a file mapping. The code will look like this:

ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = configurationFilePath;
System.Configuration.Configuration configuration =
    ConfigurationManager.OpenMappedExeConfiguration(
        fileMap,
        ConfigurationUserLevel.None);
3
dotnetnate On

I'd agree that Mike's answer is logically correct (i.e. you're not separating your configuration loading from the class - potentially), the practical matter is that for Moles host types, per your original question, you'd need to Mole the calls to the configuration system, e.g.

 MConfigurationManager.AllInstances.OpenExeConfiguration (... finish your moleing here...)

The syntax is approximate - I can't remember if you end up with an SConfigurationManager or MConfigurationManager in that case.

Where I wholly disagree with Mike is that the statement "...development workstation not be able to access external dependency systems..." is flat out horrible advice. We make these things called integration tests.

Yes, you as a developer should be creating them. At some point you will write code that touches a concrete implementation (e.g. a database, backing service, etc...) and if you're not testing that interaction, you're pretty much doing it wrong.

5
Mike Christian On

Any time you are performing unit tests, application and user settings should be passed via dependency injection. This is accomplished by creating a Stub for the settings, which is easy to do.

  1. Create an interface, in the project being tested, that includes a property for every configuration setting. Lets call this "ISettings," for reference.

  2. Create a stub (class) in the target assembly that implements the interface. Each property in the stub should contain only a get that returns the corresponding setting form the configuration file. We'll refer to this stub as "SettingsStub". This stub is used by the target assembly, in the production environment.

  3. Add an ISettings typed argument to the target type (class being tested) constructor. A field in the target class must be set to the ISettings object passed to the constructor. You may create an overload constructor, and preserve the default constructor, as required by some design patterns (MVVM, etc.). The default constructor (the one that has no arguments) may simply instantiate a new SettingsStub, for use in production. The overloaded constructor must always be used by tests!

  4. Create a settings stub to the test project, that also implements ISettings. We'll refer to this as TestSettingsStub. This stub contains hard-coded values that are acceptable for most tests.

  5. Rebuild the target and test projects. Moles generates a Stub type named SISettings.

Use the concrete TestSrtyingsStub, when you don't need to adjust any of the setting values. Alternatively, use the Miles Stub type, when the values need to be adjusted for one or two tests. The purpose of the Moles Stub types is to avoid the need to make many stubs that contain one or two unique changes.

The SettingsStub, TestSettigsStub, and SISettings types may be used interchangeably, when calling the overloaded constructor. Now, you have full control over what settings are used in each context, without the need for switching logic or manually changing setting values, during testing. The target code simply retrieves setting values from the local field, instead of directly from the configuration file. Please refer to Dependency Injection and Inversion of Control (IOC) topics.

As usual, your development workstation development not be able to access external dependency systems (database, etc.) on the production network, for safety!

Happy coding!