Search code examples
c#unit-testingabstract-classnsubstitute

Mocking domain entity class' properties in unit tests: methods, abstract classes or virtual properties?


I have a class that represents a domain entity and this class does not implement any interfaces. Let's consider something as simple as:

public class DomainEntity
{
    public DomainEntity(string name) { Name = name; }
    public string Name { get; private set; }
}

I have some other class that I'm testing. It has a method that accepts my DomainEntity as a parameter and this method accesses the Name property. For example:

public class EntityNameChecker : IEntityNameChecker
{
    public bool IsDomainEntityNameValid(DomainEntity entity)
    {
        if (entity.Name == "Valid") { return true; }

        return false;
    }
}

I have to mock my DomainEntity for my test. I'm using NSubstitute as my mocking library (it does not allow mocking non-virtual/non-abstract properties).

So, without adding interfaces (a-la IDomainEntity) I have three options for mocking the property value left.

  1. Make the Name property virtual:

    public class DomainEntity
    {
        public DomainEntity(string name) { Name = name; }
        public virtual string Name { get; }
    }
    

    The downside here is that I can no longer make my DomainEntity class sealed, which means that any consumer can inherit from it and override my Name property.

  2. Create an abstract "base" class and use the base class type as a parameter type:

    public abstract class DomainEntityBase
    {
        protected abstract string Name { get; private protected set; }
    }
    
    public sealed class DomainEntity : DomainEntityBase
    {
        public DomainEntity(string name) { Name = name; }
        protected override string Name { get; private protected set; }
    }
    
    public class EntityNameChecker : IEntityNameChecker
    {
        public bool IsDomainEntityNameValid(DomainEntityBase entity)
        {
            if (entity.Name == "Valid") { return true; }
    
            return false;
        }
    }
    

    The downside here is over-complication. And this basically turns the abstract class into an interface of some sort.

  3. Instead of accessing the Name property directly, turning Name getter into a method call to get the value (we can even go as far as making the method internal and using InternalsVisibleTo attribute to make the method visible to our test assembly):

    [assembly: InternalsVisibleToAttribute("TestAssembly")]
    public sealed class DomainEntity
    {
        private string _name;
    
        public DomainEntity(string name) { _name = name; }
    
        public string Name => GetName();
    
        internal string GetName()
        {
            return _name;
        }
    }
    

    The downside here is... Um, more code, methods, more complication (and it's not immediately obvious why it's coded this way).

My question is: is there a "preferred" way of doing that? Why is it preferred?

Edit:

  1. The reason I don’t want to simply use the instance of the class in my tests is the fact that there might be additional logic in the constructor. If I break the constructor it’s going to break all dependent tests (but it should break only the tests testing DomainEntity).

  2. I could extract an interface and be done with it. But I prefer to use interfaces to define behaviors. And these classes have none.


Solution

  • I could extract an interface and be done with it. But I prefer to use interfaces to define behaviors. And these classes have none.

    You might be making life unnecessarily difficult for yourself by avoiding interfaces here. If you truly want to "mock" the domain entities (meaning, replace production behavior with test-specific behavior), I think an interface is the way to go. However, you specifically said these classes have no behavior, so read on...

    The reason I don’t want to simply use the instance of the class in my tests is the fact that there might be additional logic in the constructor. If I break the constructor it’s going to break all dependent tests (but it should break only the tests testing DomainEntity).

    It sounds like you don't really need mocking (as I defined it above)--you just need a maintainable way to instantiate test instances.

    To solve that problem, you could introduce a builder to construct instances of DomainEntity. The builder will serve as a buffer or abstraction between your tests and the entity constructor. It can supply sane default values for any constructor arguments a particular test doesn't care about.

    Using the classes you defined in your question as a starting point, let's assume you have a test like this (using xUnit syntax):

    [Fact]
    public void Test1() {
        var entity = new DomainEntity("Valid");
    
        var nameChecker = new EntityNameChecker();
    
        Assert.True(nameChecker.IsDomainEntityNameValid(entity));
    }
    

    Now, maybe we want to add a new required property to the domain entity:

    public sealed class DomainEntity {
        public string Name { get; private set; }
        public DateTimeOffset Date { get; private set; }
    
        public DomainEntity(string name, DateTimeOffset date) {
            Name = name;
            Date = date;
        }   
    }
    

    The new constructor argument breaks the test (and probably lots of other tests).

    So we introduce a builder:

    public sealed class DomainEntityBuilder {
        public string Name { get; set; } = "Default Name";
        public DateTimeOffset Date { get; set; } = DateTimeOffset.Now;
    
        public DomainEntity Build() => new DomainEntity(Name, Date);
    }
    

    And modify our test slightly:

    [Fact]
    public void Test1()
    {
        // Instead of calling EntityBuilder's constructor, use DomainEntityBuilder
        var entity = new DomainEntityBuilder{ Name = "Valid" }.Build();
    
        var nameChecker = new EntityNameChecker();
    
        Assert.True(nameChecker.IsDomainEntityNameValid(entity));
    }
    

    The test is no longer tightly coupled to the entity's constructor. The builder provides sane defaults for all properties, and each test provides only the values that are relevant to that specific test. As a bonus, methods (or extension methods) can be added to the builder to help set up complex scenarios.

    There are libraries that can help solve this sort of problem. I've used Bogus in a few different projects. I think AutoFixture is a popular option, but I haven't used it myself. A simple builder is easy to implement, so I recommend starting with a home-brew implementation, and adding a 3rd-party library only if the home-brew implementation becomes too tedious or complicated to maintain. Because the builder is an abstraction itself, it will be easy to replace its implementation with one based on a library if/when the time comes.