1

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();
    }
}
Steven Scott
  • 10,234
  • 9
  • 69
  • 117
  • Hi, @Steven Scott. I think we could help a little better if we saw the definition (at least the signature) of `IniReader`. It sounds like you're trying to fake some methods that aren't virtual ([which isn't supported](https://github.com/FakeItEasy/FakeItEasy/wiki/What-can-be-faked)). Without the signature of the class being faked (and an indication of which line throws the exception) there's not too much we can do. – Blair Conrad Aug 16 '13 at 14:19
  • As an aside, and maybe I shouldn't confuse thing, but your original tests could be a little simpler. Your first A.CallTo is equivalent to `A.CallTo(() => FakeINI.ReadString("SignOn", "SendEnterClear")).Returns("N");` – Blair Conrad Aug 16 '13 at 14:44
  • OH! I just noticed something else. In `GetLoginSequence`, you have two ReadString methods that take _3_ parameters - what look like a section, a key, and a default value. But your `A.CallTo`s only specify 2 - the section and key. I don't think that's causing your "Non virtual methods can not be intercepted." error, but it's going to cause an error at some point, except in your very last example where you don't specify the parameters. But as you point out, that doesn't allow you to specify the different return types for various inputs. – Blair Conrad Aug 16 '13 at 14:48
  • @BlairConrad Thanks for the response. Actually, the ReadString in the INIReader has various options, including the 3 (Section, Key, Default) as you indicated, and 2 parameters (Section, Key) which then calls the 3 parameter ReadString. – Steven Scott Aug 16 '13 at 18:17
  • @BlairConrad I think you are right with the definition not being virtual, but I have these defined in the parent class as abstract, so they have to be defined in the inheriting class and implemented, which then does not allow me to add the virtual method to the abstract definition. I have added the sample code into the main question. – Steven Scott Aug 16 '13 at 18:23

1 Answers1

2

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");
}
Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • 1
    Thanks. Your direction made me look harder as well at my code, and seeing the difference between the abstract and virtual, I could use a virtual as well. However, changing as you indicated to have the extra parameter worked like a charm. Thank you very much for your assistance. – Steven Scott Aug 16 '13 at 18:56