2

I have read so much (dozens of posts) about one thing:

How to unit test business logic code that has Entity Framework code in it.

I have a WCF service with 3 layers :

  • Service Layer
  • Business Logic Layer
  • Data Access Layer

My business logic uses the DbContext for all the database operations. All my entities are now POCOs (used to be ObjectContext, but I changed that).

I have read Ladislav Mrnka's answer here and here on the reasons why we should not mock \ fake the DbContext.

He said: "That is the reason why I believe that code dealing with context / Linq-to-entities should be covered with integration tests and work against the real database."

and: "Sure, your approach works in some cases but unit testing strategy must work in all cases - to make it work you must move EF and IQueryable completely from your tested method."

My question is - how do you achieve this ???

public class TaskManager
{
    public void UpdateTaskStatus(
        Guid loggedInUserId,
        Guid clientId,
        Guid taskId,
        Guid chosenOptionId,
        Boolean isTaskCompleted,
        String notes,
        Byte[] rowVersion
    )
    {
        using (TransactionScope ts = new TransactionScope())
        {
            using (CloseDBEntities entities = new CloseDBEntities())
            {
                User currentUser = entities.Users.SingleOrDefault(us => us.Id == loggedInUserId);
                if (currentUser == null)
                    throw new Exception("Logged user does not exist in the system.");

                // Locate the task that is attached to this client
                ClientTaskStatus taskStatus = entities.ClientTaskStatuses.SingleOrDefault(p => p.TaskId == taskId && p.Visit.ClientId == clientId);
                if (taskStatus == null)
                    throw new Exception("Could not find this task for the client in the database.");

                if (taskStatus.Visit.CustomerRepId.HasValue == false)
                    throw new Exception("No customer rep is assigned to the client yet.");
                TaskOption option = entities.TaskOptions.SingleOrDefault(op => op.Id == optionId);
                if (option == null)
                    throw new Exception("The chosen option was not found in the database.");

                if (taskStatus.RowVersion != rowVersion)
                    throw new Exception("The task was updated by someone else. Please refresh the information and try again.");

                taskStatus.ChosenOptionId = optionId;
                taskStatus.IsCompleted = isTaskCompleted;
                taskStatus.Notes = notes;

                // Save changes to database
                entities.SaveChanges();
            }

            // Complete the transaction scope
            ts.Complete();
        }
    }
}

In the code attached there is a demonstration of a function from my business logic. The function has several 'trips' to the database. I don't understand how exactly I can strip the EF code from this function out to a separate assembly, so that I am able to unit test this function (by injecting some fake data instead of the EF data), and integrate test the assembly that contains the 'EF functions'.

Can Ladislav or anyone else help out?

[Edit]

Here is another example of code from my business logic, I don't understand how I can 'move the EF and IQueryable code' out from my tested method :

public List<UserDto> GetUsersByFilters(
    String ssn, 
    List<Guid> orderIds, 
    List<MaritalStatusEnum> maritalStatuses, 
    String name, 
    int age
)
{
    using (MyProjEntities entities = new MyProjEntities())
    {
        IQueryable<User> users = entities.Users;

        // Filter By SSN (check if the user's ssn matches)
        if (String.IsNullOrEmusy(ssn) == false)
            users = users.Where(us => us.SSN == ssn);

        // Filter By Orders (check fi the user has all the orders in the list)
        if (orderIds != null)
            users = users.Where(us => UserContainsAllOrders(us, orderIds));

        // Filter By Marital Status (check if the user has a marital status that is in the filter list)
        if (maritalStatuses != null)
            users = users.Where(pt => maritalStatuses.Contains((MaritalStatusEnum)us.MaritalStatus));

        // Filter By Name (check if the user's name matches)
        if (String.IsNullOrEmusy(name) == false)
            users = users.Where(us => us.name == name);

        // Filter By Age (check if the user's age matches)
        if (age > 0)
            users = users.Where(us => us.Age == age);


        return users.ToList();
    }
}

private   Boolean   UserContainsAllOrders(User user, List<Guid> orderIds)
{
    return orderIds.All(orderId => user.Orders.Any(order => order.Id == orderId));
}
Community
  • 1
  • 1
John Miner
  • 893
  • 1
  • 15
  • 32

2 Answers2

5

If you want to unit test your TaskManager class, you should employ the Repository dessign pattern and inject repositories such as UserRepository or ClientTaskStatusRepository into this class. Then instead of constructing CloseDBEntities object you will use these repositories and call their methods, for example:

User currentUser = userRepository.GetUser(loggedInUserId);
ClientTaskStatus taskStatus = 
    clientTaskStatusRepository.GetTaskStatus(taskId, clientId);

If yout wanto to integration test your TaskManager class, the solution is much more simple. You just need to initialize CloseDBEntities object with a connection string pointing to the test database and that's it. One way how to achieve this is injecting the CloseDBEntities object into the TaskManager class.

You will also need to re-create the test database before each integration test run and populate it with some test data. This can be achieved using Database Initializer.

Juraj Suchár
  • 1,107
  • 6
  • 25
  • @Juarj - thank for the reply. But from everything I read - the DbContext ITSELF is a UNIT OF WORK and each DbSet inside it is a REPOSITORY. Everyone says that when you use DbContext - you DO NOT need to create your own repositories and unit of work. so what exactly SHOULD I do ?? – John Miner Jun 08 '12 at 12:29
  • 1
    You do not need to implement the UoW and Repository logic such as change tracking, managing transactions, putting SQL queries together etc. This is already implemented by EF. But for unit testing purposes you still need a lightweight facade that hides DbContext and DbSet objects, because it is almost impossible to mock them. – Juraj Suchár Jun 08 '12 at 13:25
  • can you please offer an example of such a facade ? I am pretty clueless after 3 days of research on the subject... :( – John Miner Jun 08 '12 at 13:43
  • 1
    One example is here [Implementing the Repository and Unit of Work](http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application). It is tailored for ASP.NET MVC's controllers, but you can use it for your TaskManager class too. – Juraj Suchár Jun 08 '12 at 14:08
  • I have added another example to my question. Can you please help me out in understanding how to use REPOSITORY and UNIT OF WORK - in order to extract the 'EF and IQUERYABLE' code out from my business logic to a seperate 'testable' location ? – John Miner Jun 08 '12 at 15:52
  • As I see it - if I create a REPOSITORY - in the example you gave me - the repository function 'GetItems' performs a '.ToList()' before it returns the list, so it will always get ALL the items from the database. What happens if I want to filter items in my business logic ??? Do I have to move that logic to the repository ?? – John Miner Jun 08 '12 at 16:04
  • 1
    One solution is to create a specialized repository method for this purpose that accepts parameters such as _ssn_, _maritalStatuses_, _age_, etc. More general solution is described in the example (see the link above, chapter _Creating a Generic Repository_). There is a `Get` method, that accepts an optional parameter `Expression> filter = null`. This is in fact implementation of so-called _Query object pattern_. I would also consider the third solution - returning IQueryable from the repository. It can be reasonable trade-off between purism and costs. – Juraj Suchár Jun 11 '12 at 09:47
  • Thanks, @Juraj ! Bounty awarded :) – John Miner Jun 11 '12 at 11:08
  • a problem I have encountered with using the repo idea is when the tests run in parallel. If you drop and recreate the db on each test you will sometimes get access errors as db is in use. (i.e. two tests running at the same time). So I put a singleton flag in to only drop the first time any tests are run in that app domain. All works on local box. CI Server runs each test dll as own app so different app domains and runs them in parallel, clashing again. – Jon Aug 02 '12 at 12:41
4

There are several misunderstandings here.

First: The Repository Pattern. It's not just a facade over DbSet for unit testing! The repository is a pattenr strongly related to Aggregate and Aggreate Root concepts of Domain Driven Design. An aggregate is a set of related entities that should stay consistent to each other. I mean a business consistency, not just only a foreign keys validity. For example: a customer who have made 2 orders should get a 5% discount. So we should somehow manage the consistency between the number of order entities related to a customer entity and a discount property of the customer entity. A node responsible for this is an aggregate root. It is also the only node that should be accessible directly from outside of the aggregate. And the repository is an utility to obtain an aggregate root from some (maybe persistent) storage.

A typical use case is to create a UoW/Transaction/DbContext/WhateverYouNameIt, obtain one aggregate root entity from the repository, call some methods on it or access some other entities by traversing from the root, Commit/SaveChanges/Whatever. Look, how far it differs from yur samples.

Second: The Business Logic. I've already showed you one example: a customer who have made 2 orders should get a 5% discount. In contrary: your second code sample is not a business logic. It's just a query. The responsibility of this code is to obtain some data from the storage. In such a case, the storage technology behind it does matter. So I would recomend integration tests here rather than pretending the storage doesn't matter when interacting with the storage is the sole purpose of this function.

I would also encapsulate that in a Query Object that was already suggested. Then - such a query object could be mocked. Not just DbContext behind it. The whole QO.

The first code sample is a bit better because it probably ivolves some business logic, but that's dificult to identify. Wich leads us to the third problem.

Third: Anemic Domain Model. Your domain doesnt' look very object oriented. You have some dumb entities and transaction scripts over them. With 7 parameters! Thats pure procedural programming.

Moreover, in your UpdateTaskStatus use case - what is the aggregate root? Befere you answer that, the most important question first: what exactly do you want to do? Is that... hmm... marking a current task of a user done when he was visited? Than, maybe there should be a method Visit() inside a Customer Entity? And this method should have something like this.CurrentTaskStatus.IsCompleted = true? That was just a random guess. If I missed, that would clearly show another issue. The domain model should use the ubiquitous language - something common for the programmer and a business. Your code doesn't have that expressive power that a common language gives. I just don't know what is going on there in UpdateTaskStatus with 7 parameters.

If you place proper expressive methods for performing business operations in your entities that will also enforce you to not use DbContext there at all, as you need your entities to stay persistence ignorant. Then the problem with mocking disappears. You can test the pure business logic without persistence concerns.

So the final word: Reconsider your model first. Make your API expressive by using ubiquitous language first.

PS: Please don't treat me as an authority. I may be completely wrong as I'm just starting to learn DDD.

Pein
  • 1,216
  • 9
  • 16
  • I didn't read any, but [Udi Dahan's blog](http://udidahan.com) is a great place to start. – Pein Jun 20 '12 at 20:08