Steven Scott Steven Scott - 4 months ago 24
Ini Question

Faking a INI Configuration File Settings with FakeItEasy in C#

I have a class that inherits from the abstract Configuration class, and then each class implements the reader for INI files, XML, conf, or proprietary formats. I am having a problem in creating the objects to be tested using FakeItEasy.

The object I am trying to test uses the configuration object via Dependency Injection, so it can simply read configuration settings by calling the ReadString(), ReadInteger() etc... functions, and then the text for the location (Section, Key for instance, with an INI) may be retrieved from the appropriate section in whatever format of configuration file (INI, XML, conf, etc...).

Sample code being used:

public class TestFile
{
private readonly ConfigurationSettings ConfigurationController_ ;

...

public TestFile(ConfigurationSettings ConfigObject)
{
this.ConfigurationController_ = ConfigObject;
}

public TestFile(XMLFile ConfigObject)
{
this.ConfigurationController_ = ConfigObject;
}

public TestFile(INIFile ConfigObject)
{
this.ConfigurationController_ = ConfigObject;
}

...

private List<string> GetLoginSequence()
{
List<string> ReturnText = new List<string>();
string SignOnFirst = ConfigurationController_.ReadString("SignOn", "SignOnKeyFirst", "Y");
string SendEnterClear = ConfigurationController_.ReadString("SignOn", "SendEnterClear", "N");

if (SendEnterClear.Equals("Y", StringComparison.CurrentCultureIgnoreCase))
{
ReturnText.Add("enter");
ReturnText.Add("clear");
}

if (SignOnFirst.Equals("N", StringComparison.CurrentCultureIgnoreCase))
{
ReturnText.AddRange(MacroUserPassword("[$PASS]"));
ReturnText.Add("sign_on");
}
else
{
ReturnText.Add("sign_on");
ReturnText.AddRange(MacroUserId("[$USER]"));
ReturnText.AddRange(MacroUserPassword("[$PASS]"));
}
return ReturnText;
}


A Simple Test example:

[TestMethod]
public void TestSignOnSequence()

IniReader FakeINI = A.Fake<IniReader>();

//Sample Reads:
//Config.ReadString("SignOn", "SignOnKeyFirst", "Y");
//Config.ReadString("SignOn", "SendEnterClear", "N"); // Section, Key, Default

A.CallTo(() => FakeINI.ReadString(A<string>.That.Matches(s => s == "SignOn"), A<string>.That.Matches(s => s == "SendEnterClear"))).Returns("N");
A.CallTo(() => FakeINI.ReadString(A<string>.That.Matches(s => s == "SignOn"), A<string>.That.Matches(s => s == "SignOnKeyFirst"))).Returns("N");

A.CallTo(FakeINI).Where( x => x.Method.Name == "ReadInteger").WithReturnType<int>().Returns(1000);

TestFile TestFileObject = new TestFile(FakeINI);

List<string> ReturnedKeys = TestFileObject.GetLoginSequence();
Assert.AreEqual(2, ReturnedKeys.Count, "Ensure all keystrokes are returned");


This compiles fine, but when I execute the code, I get the following Exception:

Test threw Exception:
FakeItEasy.Configuration.FakeConfigurationException:
The current proxy generator can not intercept the specified method for the following reason:
- Non virtual methods can not be intercepted.


If I change how I create the fake which then works without an exception, I can not get different values for the various calls to the same function.

A.CallTo(FakeINI).Where( x => x.Method.Name == "ReadString").WithReturnType<string>().Returns("N");


The above method does not allow me to control the return for the different calls that the function uses to the INI.

How can I combine the two methods, the where and the test of the parameters?

Additional definitions as requested:

public abstract class ConfigurationSettings
{
...

abstract public int ReadInteger(string Section, string Key, int Default);
abstract public string ReadString(string Section, string Key, string Default);

public int ReadInteger(string Section, string Key)
{ return ReadInteger(Section, Key, 0); }

public int ReadInteger(string Key, int Default)
{ return ReadInteger("", Key, Default); }

public int ReadInteger(string Key)
{ return ReadInteger(Key, 0); }

public string ReadString(string Section, string Key)
{ return ReadString(Section, Key, null); }
}

public class IniReader : ConfigurationSettings
{
...

public IniReader()
{
}

public IniReader(string PathAndFile)
{
this.PathAndFileName = PathAndFile;
}

public override int ReadInteger(string Section, string Key, int Default)
{
return GetPrivateProfileInt(Section, Key, Default, PathAndFileName);
}

public override string ReadString(string Section, string Key, string Default)
{
StringBuilder WorkingString = new StringBuilder(MAX_ENTRY);
int Return = GetPrivateProfileString(Section, Key, Default, WorkingString, MAX_ENTRY, PathAndFileName);
return WorkingString.ToString();
}
}

Answer

You're getting the

The current proxy generator can not intercept the specified method for the following reason: - Non virtual methods can not be intercepted.

error because you're trying to fake the 2-parameter version of ReadString. Only virtual members, abstract members, or interface members can be faked. Since your two-paremter ReadString is none of these, it can't be faked. I think you should either have a virtual 2-parameter ReadString or fake the 3-parameter ReadString.

Your example pushes me towards faking the 3-parameter ReadString, especially since GetLoginSequence uses that one. Then I think you could just constrain using the expressions (rather than method name strings) and it would all work out.

I made a little test with bits of your code (mostly from before your update) and had success with faking the 3-parameter ReadString:

[Test]
public void BlairTest()
{
    IniReader FakeINI = A.Fake<IniReader>();

    A.CallTo(() => FakeINI.ReadString("SignOn", "SendEnterClear", A<string>._)).Returns("N");
    A.CallTo(() => FakeINI.ReadString("SignOn", "SignOnKeyFirst", A<string>._)).Returns("N");

    // Personally, I'd use the above syntax for this one too, but I didn't
    // want to muck too much.
    A.CallTo(FakeINI).Where(x => x.Method.Name == "ReadInteger").WithReturnType<int>().Returns(1000);


    TestFile TestFileObject = new TestFile(FakeINI);

    List<string> ReturnedKeys = TestFileObject.GetLoginSequence();
    Assert.AreEqual(2, ReturnedKeys.Count, "Ensure all keystrokes are returned");
}