Search code examples
listunit-testingdesign-patternsdomain-driven-designaggregateroot

Unit Testing a Domain Model Containing Lists Populated From The Database


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;
    }

Solution

  • 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.