2

Edit: going to expose production code on SO! Hope no one steals my secrets!

I have a Controller class for communicating with a device over TCP using the Modbus protocol. I use the NModbus library.

Here are the interfaces the controller class implements:

public interface CoilReader
{
    bool[] Read(ushort startAddress, ushort numberOfCoils);
}

public interface CoilWriter
{
    void WriteSingle(ushort address, bool value);

    void WriteMultiple(ushort startAddress, bool[] values);
}

public interface HoldingRegisterReader
{
    ushort[] Read(ushort startAddress, ushort numberOfRegisters);
}

public interface HoldingRegisterWriter
{
    void WriteSingle(ushort address, ushort value);

    void WriteMultiple(ushort startAddress, ushort[] values);
}

public interface InputReader
{
    bool[] Read(ushort startAddress, ushort numberOfCoils);
}

public interface InputRegisterReader
{
    ushort[] Read(ushort startAddress, ushort numberOfRegisters);
}

public interface ConnectionInfo
{
    string IP { get; set; }
}

Here is the controller class.

using System;
using System.Net.Sockets;
using System.Reflection;
using global::Modbus.Device;

public class Controller
    : ConnectionInfo,
      HoldingRegisterReader,
      InputRegisterReader,
      CoilReader,
      InputReader,
      CoilWriter,
      HoldingRegisterWriter
{
    static Controller()
    {
        AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => loadEmbeddedAssembly(e.Name);
    }

    public virtual string IP
    {
        get
        {
            return this.ip;
        }

        set
        {
            this.ip = value;
        }
    }

    public virtual ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
        }
    }

    public virtual ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadInputRegisters(startAddress, numberOfRegisters);
        }
    }

    public virtual bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadCoils(startAddress, numberOfCoils);
        }
    }

    public virtual bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadInputs(startAddress, numberOfInputs);
        }
    }

    public virtual void WriteSingleCoil(ushort address, bool value)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteSingleCoil(address, value);
        }
    }

    public virtual void WriteMultipleCoils(ushort startAddress, bool[] values)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteMultipleCoils(startAddress, values);
        }
    }

    public virtual void WriteSingleHoldingRegister(ushort address, ushort value)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteSingleRegister(address, value);
        }
    }

    public virtual void WriteMultipleHoldingRegisters(ushort startAddress, ushort[] values)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteMultipleRegisters(startAddress, values);
        }
    }

    string ConnectionInfo.IP
    {
        get
        {
            return this.IP;
        }

        set
        {
            this.IP = value;
        }
    }

    ushort[] HoldingRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
    {
        return this.ReadHoldingRegisters(startAddress, numberOfRegisters);
    }

    ushort[] InputRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
    {
        return this.ReadInputRegisters(startAddress, numberOfRegisters);
    }

    bool[] CoilReader.Read(ushort startAddress, ushort numberOfCoils)
    {
        return this.ReadCoils(startAddress, numberOfCoils);
    }

    bool[] InputReader.Read(ushort startAddress, ushort numberOfInputs)
    {
        return this.ReadInputs(startAddress, numberOfInputs);
    }

    void CoilWriter.WriteSingle(ushort address, bool value)
    {
        this.WriteSingleCoil(address, value);
    }

    void CoilWriter.WriteMultiple(ushort startAddress, bool[] values)
    {
        this.WriteMultipleCoils(startAddress, values);
    }

    void HoldingRegisterWriter.WriteSingle(ushort address, ushort value)
    {
        this.WriteSingleHoldingRegister(address, value);
    }

    void HoldingRegisterWriter.WriteMultiple(ushort startAddress, ushort[] values)
    {
        this.WriteMultipleHoldingRegisters(startAddress, values);
    }

    private ModbusIpMaster createDeviceConnection()
    {
        const int port = 502;
        var client = new TcpClient();
        client.BeginConnect(this.ip, port, null, null).AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
        if (!client.Connected)
        {
            throw new Exception("Cannot connect to " + this.ip + ":" + port);
        }

        return ModbusIpMaster.CreateIp(client);
    }

    private static Assembly loadEmbeddedAssembly(string name)
    {
        if (name.EndsWith("Retargetable=Yes"))
        {
            return Assembly.Load(new AssemblyName(name));
        }

        var container = Assembly.GetExecutingAssembly();
        var path = new AssemblyName(name).Name + ".dll";

        using (var stream = container.GetManifestResourceStream(path))
        {
            if (stream == null)
            {
                return null;
            }

            var bytes = new byte[stream.Length];
            stream.Read(bytes, 0, bytes.Length);
            return Assembly.Load(bytes);
        }
    }

    private string ip;
}

I do not get the following error from a test created in a Tests project in the same solution as the library that contains this class and its interfaces. However, in a project in a different solution, that consumes the library, I get the following:

------ Test started: Assembly: CareControls.IvisHmi.Tests.dll ------

Unknown .NET Framework Version: v4.5.1
Test 'CareControls.IvisHmi.Tests.Presenters.ModbusTcpTogglePresenterTests.FactMethodName' failed:     FakeItEasy.Core.FakeCreationException : 
Failed to create fake of type "CareControls.Modbus.Tcp.Controller".

Below is a list of reasons for failure per attempted constructor:
  No constructor arguments failed:
    No default constructor was found on the type CareControls.Modbus.Tcp.Controller.

If either the type or constructor is internal, try adding the following attribute to the assembly:
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]


at FakeItEasy.Core.DefaultExceptionThrower.ThrowFailedToGenerateProxyWithResolvedConstructors(Type typeOfFake, String reasonForFailureOfUnspecifiedConstructor, IEnumerable`1 resolvedConstructors)
at FakeItEasy.Creation.FakeObjectCreator.TryCreateFakeWithDummyArgumentsForConstructor(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, String failReasonForDefaultConstructor, Boolean throwOnFailure)
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, Boolean throwOnFailure)
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, FakeOptions options)
at FakeItEasy.Creation.DefaultFakeCreatorFacade.CreateFake[T](Action`1 options)
at FakeItEasy.A.Fake[T]()
Presenters\ModbusTcpTogglePresenterTests.cs(22,0): at CareControls.IvisHmi.Tests.Presenters.ModbusTcpTogglePresenterTests.FactMethodName()

0 passed, 1 failed, 0 skipped, took 0.93 seconds (xUnit.net 1.9.2 build 1705).

This is the test:

namespace CareControls.IvisHmi.Tests.Presenters
{
    using CareControls.IvisHmi.Framework;
    using CareControls.IvisHmi.Presenters;
    using CareControls.IvisHmi.UI;
    using CareControls.Modbus.Tcp;
    using FakeItEasy;
    using Ploeh.AutoFixture;
    using Xunit;

    public class ModbusTcpTogglePresenterTests
    {
        [Fact]
        public void FactMethodName()
        {
            A.Fake<Controller>();
        }
    }
}

Why does FakeItEasy not think there is a default constructor on the class?

Sorry for the massive post, but it was requested I include code.

Edit: the test passes if I add new Controller() before the A.Fake<Controller>() line:

namespace CareControls.IvisHmi.Tests.Presenters
{
    using CareControls.IvisHmi.Framework;
    using CareControls.IvisHmi.Presenters;
    using CareControls.IvisHmi.UI;
    using CareControls.Modbus.Tcp;
    using FakeItEasy;
    using Ploeh.AutoFixture;
    using Xunit;

    public class ModbusTcpTogglePresenterTests
    {
        [Fact]
        public void FactMethodName()
        {
            new Controller();
            A.Fake<Controller>();
        }
    }
}
xofz
  • 5,600
  • 6
  • 45
  • 63
  • 1
    could you pleae share your code? – Sudhakar Tillapudi Mar 14 '14 at 19:24
  • 1
    Let's see SomeClass and AnotherClass – Bob Provencher Mar 14 '14 at 19:25
  • The error message complains about `AnotherClass` not having a constructor, but you mention that *"`SomeClass` does have a default constructor!"* in your post. Have you checked `AnotherClass`? – juan.facorro Mar 14 '14 at 19:32
  • I think I fumbled my translating of the actual code to code for public viewing. I'm going to take a risk and post the production code. – xofz Mar 14 '14 at 19:35
  • I don't explicitly declare the default constructor, I let the compiler generate one for me. The additional static constructor is there to load the embedded NModbus library when it's needed. – xofz Mar 14 '14 at 19:46
  • 1
    Can you try to remove bits and pieces from the code, or perhaps even make a duplicate of the class that you can muck around with, then remove bits and pieces one step at a time until it either works or you're left with something small? – Lasse V. Karlsen Mar 14 '14 at 19:51
  • And just to be clear. That is the *entire* file? Copied from your code, not modified in anyway? Just so that we don't discover later than in fact it's a partial class, and that there was in fact a private constructor declared in a different file. – Lasse V. Karlsen Mar 14 '14 at 19:54
  • As @LasseV.Karlsen said - obviously it is not the whole file - the class in sample does not even have namespace mentioned in the exception. – Alexei Levenkov Mar 14 '14 at 19:57
  • It is the entire class, with the namespace declaration removed. – xofz Mar 14 '14 at 20:06
  • What version of FakeItEasy are you using? Recent versions have included some improvements in the error reporting when we're unable to create a Fake. I'm not promising anything, but I think if you move to the latest version (1.18.0, as I type), we may get more clues. – Blair Conrad Mar 15 '14 at 11:26
  • I was using 1.13.1. I updated to 1.18.0 and got the same message, with an explanation of the exception FakeItEasy caught: Could not load file or assembly 'Modbus, Version=1.11.0.0, Culture=neutral, PublicKeyToken=b5aba55fcbc8d946' or one of its dependencies. The system cannot find the file specified. So it looks like the way in which FakeItEasy/DynamicProxy creates instances of types doesn't actually cause the static constructor to be invoked?! I don't really understand why the AssemblyResolve event isn't being subscribed to. – xofz Mar 15 '14 at 17:54
  • Yup, it's the propagation of the exception message that was added. I'm glad it's providing value. Static constructors are called "… automatically to initialize the class before the first instance is created or any static members are referenced." (from http://msdn.microsoft.com/en-us/library/k9x6w0hc(v=vs.110).aspx). Certainly FIE isn't going out of its way to do either of those things. I guess DynamicProxy doesn't either. You could always put a breakpoint in the static constructor to be sure, but my guess is that access to the class triggers the assembly load, before the static constructor. – Blair Conrad Mar 15 '14 at 22:58

4 Answers4

2

According to this:

Faking/mocking an interface gives "no default constructor" error, how can that be?

there is a bug where the wrong exception message can be given. They say that in that case there should still be something causing the exception to be thrown, though.

Things I would suggest:

  • Does adding an explicit default constructor help?
  • Can you successfully instantiate the class normally?
  • Can you successfully instantiate the class with reflection or Activator.CreateInstance?
Community
  • 1
  • 1
Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
  • `Activator.CreateInstance(typeof(Controller))` and `new Controller()` both work. In addition, when I added one of these lines before the `A.Fake()` line, the test passed! What the heck is going on? – xofz Mar 14 '14 at 20:09
  • @SamPearson It could be something like that the assembly hasn't been loaded yet, so the reflection that the FakeItEasy uses can't find the class. By using the class in your code before doing the "faking", you're causing the assembly to be loaded, and the type to become visible. Not 100% sure, just a guess, but I have had similar issues before. – Dave Cousineau Mar 14 '14 at 21:26
  • Referencing the type, as is done in `` should be enough to load the assembly. – Lasse V. Karlsen Mar 15 '14 at 11:43
  • There have been improvements made in recent FakeItEasy versions around the "bad error" problem. Version 1.14.0 and above should give better hints. Excellent suggestions, BTW. – Blair Conrad Mar 15 '14 at 12:19
1

A static constructor is not the same thing as a public constructor. Are you sure the compiler generates a default public constructor when a static constructor is defined? I wouldn't have thought you'd get both. Does it show up if you use ILDASM to reverse engineer the assembly?

The static constructor may well be called 'lazily' so that when FakeItEasy tries to create a fake by reflecting over the type, the static constructor hasn't yet been called. This would explain why it works when you instantiate the type before creating a fake.

Tim Long
  • 13,508
  • 19
  • 79
  • 147
1

To summarize information gathered by Sam Pearson using FakeItEasy 1.18.0 as well as MSDN documentation the problem arises from an an exception thrown when the Modbus assembly can't be loaded. This is interesting since the Controller's static constructor contains code to deal with this failure, so it must not have been executed. Static constructors are called

… automatically to initialize the class before the first instance is created or any static members are referenced.1

However, FakeItEasy doesn't do either of those things intentionally. It seems that DynamicProxy doesn't either.

My guess is that accessing the class so it can be faked triggers the Modbus assembly load, without triggering the static constructor.

However, it's possible that FakeItEasy's assembly scanning is triggering the assembly load.
This can be checked by adding a test that fakes out something else, like ICollection, and running that instead.
If the error still occurs, then it's the directory scanning that's doing it. In that case, you can use the new Bootstrapper added in FakeItEasy 1.18.0 to disable the directory scanning. We have no official documentation yet, as there's an ongoing discussion about changing the scanning mechanism a little, but I did post about how to disable the scanning of on-disk assemblies. If FakeItEasy's scanning triggers the load, and disabling the scanning defers the assembly load until after the static constructor is called, this may be the easiest thing to do.

Additional possible workarounds:

  1. instantiating the Controller first is one workaround (as noted earlier)
  2. faking one (or all) of the many interfaces that the Controller implements instead of faking the Controller (my personal preference is to fake interfaces, not classes, but sometimes there's a need)
  3. if it's possible, perhaps best would be to address the cause of the failed assembly load, but I bet if that was an option the static constructor wouldn't contain the code that it does.
Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • Hi Blair, thanks so much for your answer. The cause of the failed assembly load is that I'm embedding the NModbus assembly into the assembly which contains `Controller`. – xofz Mar 20 '14 at 15:20
  • I tried creating a `NoExternalScanningBootstrapper` like you mentioned in your post, but I don't know how to configure FakeItEasy to use that bootstrapper. – xofz Mar 20 '14 at 15:21
  • If there is a better place to handle the assembly load for an embedded assembly inside another assembly, please let me know! For executables, I just subscribe at the entry point, but this is the closest I can get to the 'entry point' for this class. – xofz Mar 20 '14 at 15:26
  • I just added a breakpoint at `GetAssemblyFileNamesToScanForExtensions` and it *is* being reached, but I'm still getting the `FakeCreationException`. – xofz Mar 20 '14 at 15:28
  • I learned how to load embedded assemblies from a non-executable assembly from Cheeso's answer here: http://stackoverflow.com/questions/222655/embedding-assemblies-inside-another-assembly – xofz Mar 20 '14 at 15:32
  • The NoExternalScanningBootstrapper should just be picked up, if its assembly is already loaded in your AppDomain. Sounds like you have that working. I don't know anything about handling the assembly load for an embedded assembly inside another assembly. Sorry. That suggests (to me) that it's not the on-disk assembly scanning causing the assembly load to fire, but rather loading of the NModbus assembly as usually happens. Good of you to check. I think we're left back with the workarounds 1–3. 1 and 2 are straightforward, I think, and I don't have any knowledge to add to 3. :-( – Blair Conrad Mar 20 '14 at 15:56
0

I solved the problem by turning the createDeviceConnection() method into an NModbusDeviceConnection disposable class. In the static constructor of the NModbusDeviceConnection is where the AssemblyResolve event is subscribed to. Now I can create fakes of Controller without it triggering the Modbus assembly load.

For details, here is the NModbusDeviceConnection class:

// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="NModbusDeviceConnection.cs" company="Care Controls">
//   Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------

namespace CareControls.Modbus.Tcp.Internal
{
    using System;
    using System.Net.Sockets;
    using System.Reflection;
    using global::Modbus.Device;

    internal sealed class NModbusDeviceConnection : IDisposable
    {
        static NModbusDeviceConnection()
        {
            AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => loadEmbeddedAssembly(e.Name);
        }

        public NModbusDeviceConnection(string ip)
        {
            const int port = 502;
            var client = new TcpClient();
            client.BeginConnect(ip, port, null, null).AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
            if (!client.Connected)
            {
                throw new Exception("Cannot connect to " + ip + ":" + port);
            }

            this.connection = ModbusIpMaster.CreateIp(client);
        }

        public ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            return this.connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
        }

        public ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            return this.connection.ReadInputRegisters(startAddress, numberOfRegisters);
        }

        public bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
        {
            return this.connection.ReadCoils(startAddress, numberOfCoils);
        }

        public bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
        {
            return this.connection.ReadInputs(startAddress, numberOfInputs);
        }

        public void WriteSingleCoil(ushort address, bool value)
        {
            this.connection.WriteSingleCoil(address, value);
        }

        public void WriteMultipleCoils(ushort startAddress, bool[] values)
        {
            this.connection.WriteMultipleCoils(startAddress, values);
        }

        public void WriteSingleHoldingRegister(ushort address, ushort value)
        {
            this.connection.WriteSingleRegister(address, value);
        }

        public void WriteMultipleHoldingRegisters(ushort address, ushort[] values)
        {
            this.connection.WriteMultipleRegisters(address, values);
        }

        public void Dispose()
        {
            if (this.connection != null)
            {
                this.connection.Dispose();
            }
        }

        private static Assembly loadEmbeddedAssembly(string name)
        {
            if (name.EndsWith("Retargetable=Yes"))
            {
                return Assembly.Load(new AssemblyName(name));
            }

            var container = Assembly.GetExecutingAssembly();
            var path = new AssemblyName(name).Name + ".dll";

            using (var stream = container.GetManifestResourceStream(path))
            {
                if (stream == null)
                {
                    return null;
                }

                var bytes = new byte[stream.Length];
                stream.Read(bytes, 0, bytes.Length);
                return Assembly.Load(bytes);
            }
        }

        private readonly ModbusIpMaster connection;
    }
}

and here is the modified Controller class:

// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="Controller.cs" company="Care Controls">
//   Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------

namespace CareControls.Modbus.Tcp
{
    using System;
    using CareControls.Modbus.Tcp.Internal;

    public class Controller
        : ConnectionInfo,
          HoldingRegisterReader,
          InputRegisterReader,
          CoilReader,
          InputReader,
          CoilWriter,
          HoldingRegisterWriter
    {
        public Controller()
        {
            this.newDeviceConnection = () => new NModbusDeviceConnection(this.IP);
        }

        public virtual string IP { get; set; }

        public virtual ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
            }
        }

        public virtual ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadInputRegisters(startAddress, numberOfRegisters);
            }
        }

        public virtual bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadCoils(startAddress, numberOfCoils);
            }
        }

        public virtual bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadInputs(startAddress, numberOfInputs);
            }
        }

        public virtual void WriteSingleCoil(ushort address, bool value)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteSingleCoil(address, value);
            }
        }

        public virtual void WriteMultipleCoils(ushort startAddress, bool[] values)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteMultipleCoils(startAddress, values);
            }
        }

        public virtual void WriteSingleHoldingRegister(ushort address, ushort value)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteSingleHoldingRegister(address, value);
            }
        }

        public virtual void WriteMultipleHoldingRegisters(ushort startAddress, ushort[] values)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteMultipleHoldingRegisters(startAddress, values);
            }
        }

        string ConnectionInfo.IP
        {
            get
            {
                return this.IP;
            }

            set
            {
                this.IP = value;
            }
        }

        ushort[] HoldingRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
        {
            return this.ReadHoldingRegisters(startAddress, numberOfRegisters);
        }

        ushort[] InputRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
        {
            return this.ReadInputRegisters(startAddress, numberOfRegisters);
        }

        bool[] CoilReader.Read(ushort startAddress, ushort numberOfCoils)
        {
            return this.ReadCoils(startAddress, numberOfCoils);
        }

        bool[] InputReader.Read(ushort startAddress, ushort numberOfInputs)
        {
            return this.ReadInputs(startAddress, numberOfInputs);
        }

        void CoilWriter.WriteSingle(ushort address, bool value)
        {
            this.WriteSingleCoil(address, value);
        }

        void CoilWriter.WriteMultiple(ushort startAddress, bool[] values)
        {
            this.WriteMultipleCoils(startAddress, values);
        }

        void HoldingRegisterWriter.WriteSingle(ushort address, ushort value)
        {
            this.WriteSingleHoldingRegister(address, value);
        }

        void HoldingRegisterWriter.WriteMultiple(ushort startAddress, ushort[] values)
        {
            this.WriteMultipleHoldingRegisters(startAddress, values);
        }

        private readonly Func<NModbusDeviceConnection> newDeviceConnection;
    }
}
xofz
  • 5,600
  • 6
  • 45
  • 63