1

I am currently in the middle of writing unit tests for my domain models.

To lay a bit of context I have a Role Group class which has a list of Roles, it also has a list of Users that currently have this Role Group assigned to them.

A Role Group is assigned to a User so all the methods to do this are in the User domain model. This means the Users property on the Role Group side is basically pulled in from the database.

Both Role Group and User are Aggregate Roots and they can both exist on their own.

Unit Testing a Domain Model Containing Lists Populated From The Database

What I am struggling with is I can not test the CanArchive method below because I have no way off adding in a User to the property. Apart from the bad was of using the Add method which I don't want to use as it break the whole idea of Domain Models controlling their own Data.

So I am not sure if my Domain Models are wrong or if this logic should be placed in a Service as it is an interaction between two Aggregate Roots.

The Role Group Class:

    public bool Archived { get; private set; }

    public int Id { get; private set; }

    public string Name { get; private set; }

    public virtual IList<Role> Roles { get; private set; }

    public virtual IList<User> Users { get; private set; }

Updating Archived Method:

    private void UpdateArchived(bool archived)
    {
        if (archived && !CanArchive())
        {
            throw new InvalidOperationException("Role Group can not be archvied.");
        }

        Archived = archived;
    }

Method to check if Role Group can be Archived

    private bool CanArchive()
    {
        if (Users.Count > 0)
        {
            return false;
        }

        return true;
    }

Method that sets the User's Role Group in the User class This is called when a user is created or update in the user interface.

    private void UpdateRoleGroup(RoleGroup roleGroup)
    {
        if (roleGroup == null)
        {
            throw new ArgumentNullException("roleGroup", "Role Group can not be null.");
        }

        RoleGroup = roleGroup;
    }
user1866606
  • 113
  • 1
  • 1
  • 5
  • Can you post the bit that populates the `IList Users` in your standard application flow? – NikolaiDante Jan 08 '16 at 11:15
  • I've added the method in the User class that set the Role Group of the User. The only thing i could think of it getting the UpdateRoleGroup method to add or remove the user from the RoleGroup's User list but that doesn't feel quite right. – user1866606 Jan 08 '16 at 11:31
  • I'm still not quite seeing where the User list on RoleGroup gets populated. Normally that's what I would need to interact with in the setup / arrange portion of my Unit Test. – NikolaiDante Jan 08 '16 at 11:45
  • This is populated straight from the database using EF, which can push data into a public property with a private setter. There is a one to many mapping in the EF code. Saying a User can have 1 role group but a role group can have many users attached to it. So for role group the user property is more read only. Hope this clears it up a bit. – user1866606 Jan 08 '16 at 12:03
  • EF isn't especially my bag, so I'm not too sure around all that :) The only other options I can think of then if there is nothing in the code that does it, is either mocking the `DBContext` (can't help with this one) or instead of private using internal, and having the test library use the `InternalsVisibleTo`. If `InternalsVisbleTo` is an option let me know and I'll write up a proper answer. – NikolaiDante Jan 08 '16 at 12:08

2 Answers2

1

A few thoughts :

  • Unit testing a domain object should not rely upon persistence layer stuff. As soon as you do that, you have an integration test.

  • Integrating changes between two aggregates through the database is theoretically not a good idea in DDD. Changes caused by User.UpdateRoleGroup() should either stay in the User aggregate, or trigger public domain methods on other aggregates to mutate them (in an eventually consistent way). If those methods are public, they should be accessible from tests as well.

  • With Entity Framework, none of that matters really since it is not good at modelling read-only collections and you'll most likely have a mutable Users list. I don't see calling roleGroup.Users.Add(...) to set up your data in a test as a big problem, even though you should not do it in production code. Maybe making the list internal with internalsVisibleTo your test project - as @NikolaiDante suggests - and wrapping it into a public readonly collection would make that a bit less dangerous.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
0

What I am struggling with is I can not test the CanArchive method below because I have no way off adding in a User to the property.

How does the framework that loads the RoleGroup instances from the database populate the users? This is the question you must ask yourself to find the solution for your unit tests. Just do it the same way.

I don't know what language you use. In Java for example you can use the reflection api to set private fields. There are also a lot of test frameworks that provide convenience methods for this job, e.g. Deencapsulation or Whitebox.

René Link
  • 48,224
  • 13
  • 108
  • 140