Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep your unit tests simple and isolated and still guarantee DDD invariants?

DDD recommends that the domain objects should be in a valid state at any time. Aggregate roots are responsible for guaranteeing the invariants and Factories for assembling objects with all the required parts so that they are initialized in a valid state.

However this seems to complicate the task of creating simple, isolated unit tests a lot.

Let's assume we have a BookRepository that contains Books. A Book has :

  • an Author
  • a Category
  • a list of Bookstores you can find the book in

These are required attributes : a book has to have an author, a category and at least a book store you can buy the book from. There's likely to be a BookFactory since it is quite a complex object, and the Factory will initialize the Book with at least all the mentioned attributes. Maybe we'll also make the Book constructor private (and the Factory nested) so that no one can instantiate an empty Book except the Factory.

Now we want to unit test a method of the BookRepository that returns all the Books. To test if the method returns the books, we have to set up a test context (the Arrange step in AAA terms) where some Books are already in the Repository.

In C# :

[Test]
public void GetAllBooks_Returns_All_Books() 
{
    //Lengthy and messy Arrange section
    BookRepository bookRepository = new BookRepository();
    Author evans = new Author("Evans", "Eric");
    BookCategory category = new BookCategory("Software Development");
    Address address = new Address("55 Plumtree Road");
    BookStore bookStore = BookStoreFactory.Create("The Plum Bookshop", address);
    IList<BookStore> bookstores = new List<BookStore>() { bookStore };
    Book domainDrivenDesign = BookFactory.Create("Domain Driven Design", evans, category, bookstores);
    Book otherBook = BookFactory.Create("other book", evans, category, bookstores);
    bookRepository.Add(domainDrivenDesign);
    bookRepository.Add(otherBook);

    IList<Book> returnedBooks = bookRepository.GetAllBooks();

    Assert.AreEqual(2, returnedBooks.Count);
    Assert.Contains(domainDrivenDesign, returnedBooks);
    Assert.Contains(otherBook, returnedBooks);
}

Given that the only tool at our disposal to create Book objects is the Factory, the unit test now uses and is dependent on the Factory and inderectly on Category, Author and Store since we need those objects to build up a Book and then place it in the test context.

Would you consider this is a dependency in the same way that in a Service unit test we would be dependent on, say, a Repository that the Service would call ?

How would you solve the problem of having to re-create a whole cluster of objects in order to be able to test a simple thing ? How would you break that dependency and get rid of all these Book attributes we don't need in our test ? By using mocks or stubs ?

If you mock up things a Repository contains, what kind of mock/stubs would you use as opposed to when you mock up something the object under test talks to or consumes ?

like image 891
guillaume31 Avatar asked May 14 '10 10:05

guillaume31


People also ask

What is the difference between unit testing and integration testing?

Unit tests test a single piece of code, while integration tests test modules of code to understand how they work individually and interact with each other. Unit tests are fast and easy to run because they “mock out” external dependencies.

What are the first principles of unit testing?

Let’s learn about these FIRST principles – in detail. Unit tests should be fast otherwise they will slow down your development/deployment time and will take longer time to pass or fail. Typically on a sufficiently large system, there will be a few thousand unit tests – let’s say 2000 unit tests.

How do you deal with external dependencies in unit tests?

When writing unit tests, you’ll inevitably have to deal with the external dependencies your code interacts with. The usual way to go about this is using mechanisms that replace the external dependencies during testing.

Should I unit test new features?

It is common for testers to not only test their new feature but also features that existed beforehand in order to verify that previously implemented features still function as expected. With unit testing, it's possible to rerun your entire suite of tests after every build or even after you change a line of code.


1 Answers

Two things:

  • Use mock objects within the tests. You're currently using concrete objects.

  • With regards the complex set up, at some point you will need some valid books. Extract this logic to a set up method, to run before each test. Have that set up method create a valid collection of books and so forth.

"How would you solve the problem of having to re-create a whole cluster of objects in order to be able to test a simple thing ? How would you break that dependency and get rid of all these Book attributes we don't need in our test ? By using mocks or stubs ?"

A mock object would let you do this. If a test only needs a book with a valid author, your mock object would specify that author, the other attributes would be defaulted. As your test only cares about a valid author, there is no need to set up the other attributes.

like image 143
Finglas Avatar answered Sep 28 '22 00:09

Finglas