2

This was the anemic domain model:

public partial class Person
{
    public virtual int PersonId { get; internal protected set; }
    public virtual string Title { get; internal protected set; } 
    public virtual string FirstName { get; internal protected set; } 
    public virtual string MiddleName { get; internal protected set; } 
    public virtual string LastName { get; internal protected set; } 
}

And this is its behavior:

public static class Services
{

    public static void UpdatePerson(Person p, string firstName, string lastName)
    {
        // validate  firstname and lastname
        // if there's a curse word, throw an exception


        // if valid, continue

        p.FirstName = firstName;
        p.LastName = lastName;


        p.ModifiedDate = DateTime.Now;
    }

}

And it's pretty much testable:

[TestMethod]

public void Is_Person_ModifiedDate_If_Updated()
{
    // Arrange
    var p = new Mock<Person>();

    // Act 
    Services.UpdatePerson(p.Object, "John", "Lennon");

    // Assert            
    p.VerifySet(x => x.ModifiedDate = It.IsAny<DateTime>());
}

However, I wanted to practice Rich Domain Model, where data and behavior is more logically-cohesive. So the code above is now converted to:

public partial class Person
{
    public virtual int PersonId { get; internal protected set; }
    public virtual string Title { get; internal protected set; }
    public virtual string FirstName { get; internal protected set; } 
    public virtual string MiddleName { get; internal protected set; }
    public virtual string LastName { get; internal protected set; } 

    public virtual void UpdatePerson(string firstName, string lastName)
    {
        // validate  firstname and lastname
        // if there's a curse word, throw an exception


        // if valid, continue


        this.FirstName = firstName;
        this.LastName = lastName;

        this.ModifiedDate = DateTime.Now;
    }           
}

However I encounter testing problem:

[TestMethod]
public void Is_Person_ModifiedDate_If_Updated()
{
    // Arrange
    var p = new Mock<Person>();

    // Act 
    p.Object.UpdatePerson("John", "Lennon");

    // Assert            
    p.VerifySet(x => x.ModifiedDate = It.IsAny<DateTime>());
}

Unit test error:

Result Message: 

Test method Is_Person_ModifiedDate_If_Updated threw exception: 
Moq.MockException: 
Expected invocation on the mock at least once, but was never performed: x => x.ModifiedDate = It.IsAny<DateTime>()
No setups configured.

Performed invocations:
Person.UpdatePerson("John", "Lennon")
Result StackTrace:  
at Moq.Mock.ThrowVerifyException(MethodCall expected, IEnumerable`1 setups, IEnumerable`1 actualCalls, Expression expression, Times times, Int32 callCount)
   at Moq.Mock.VerifyCalls(Interceptor targetInterceptor, MethodCall expected, Expression expression, Times times)
   at Moq.Mock.VerifySet[T](Mock`1 mock, Action`1 setterExpression, Times times, String failMessage)
   at Moq.Mock`1.VerifySet(Action`1 setterExpression)
   at Is_Person_ModifiedDate_If_Updated()

Seeing that directly invoking a method from the mocked's Object, the mocked object then can't detect if any of its property or method was called. Having noticed that, what's the proper way to unit test a Rich Domain Model?

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
Hao
  • 8,047
  • 18
  • 63
  • 92
  • Thanks for undeleting, as you can see below, it took some time to write answer :) – Sergey Berezovskiy Dec 31 '13 at 13:44
  • 1
    Update methods, while beneficial to encapsulate for validation purposes as you have shown, really are just transactional scripts bleeding into the domain. Try breaking out what the user is requesting. Are they Correcting the Spelling of the person's name, or maybe Changing the Legal Name of the person. Of course this will depend upon your application, but try to think in fine grained requests and the resulting behavior when carried out. – Wes Jan 04 '14 at 06:24

2 Answers2

5

First, don't mock value objects or classes you are testing. Also you are not verifying that correct modification date was provided to person. You check that some date was assigned. But that does not prove your code works as expected. In order to tests such code you should mock current date returned by DateTime.Now, or create some abstraction, which will provide current time to service. Your first test should look like (I used Fluent Assertions and NUnit here):

[Test]
public void Should_Update_Person_When_Name_Is_Correct()
{
    // Arrange
    var p = new Person(); // person is a real class
    var timeProviderMock = new Mock<ITimeProvider>();
    var time = DateTime.Now;
    timeProviderMock.Setup(tp => tp.GetCurrentTime()).Returns(time);
    Services.TimeProvider = timeProviderMock.Object;
    // Act 
    Services.UpdatePerson(p, "John", "Lennon");
    // Assert
    p.FirstName.Should().Be("John");
    p.LastName.Should().Be("Lennon");
    p.ModifiedDate.Should().Be(time); // verify that correct date was set
    timeProviderMock.VerifyAll();
}

Time provider is a simple abstraction:

public interface ITimeProvider
{
    DateTime GetCurrentTime();
}

I'd go with singleton service instead of static class, because static classes are always problem - high coupling, no abstraction, hard to unit-test dependent classes. But you can inject time provider via property:

public static class Services
{
    public static ITimeProvider TimeProvider { get; set; }

    public static void UpdatePerson(Person p, string firstName, string lastName)
    {
        p.FirstName = firstName;
        p.LastName = lastName;
        p.ModifiedDate = TimeProvider.GetCurrentTime();
    }
}

Same relates to your second test. Do not mock object you are testing. You should verify real code, which your application will use, instead of testing some mock, which is used only by test. Test with reach domain model will look like:

[Test]
public void Should_Update_Person_When_Name_Is_Correct()
{
    // Arrange        
    var timeProviderMock = new Mock<ITimeProvider>();
    var time = DateTime.Now;
    timeProviderMock.Setup(tp => tp.GetCurrentTime()).Returns(time);
    var p = new Person(timeProviderMock.Object); // person is a real class
    // Act 
    p.Update("John", "Lennon");
    // Assert
    p.FirstName.Should().Be("John");
    p.LastName.Should().Be("Lennon");
    p.ModifiedDate.Should().Be(time); // verify that correct date was set
    timeProviderMock.VerifyAll();
}
Community
  • 1
  • 1
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • 1
    Looks like a sound solution, I should rethink when to mock or not to mock. Perhaps I should lean more on testing the actual object, and use mocked object only in situations when it's foolish to test the actual object, e.g. component that downloads a file and waiting for it to finish – Hao Dec 31 '13 at 14:57
  • 1
    I'd consider passing date in the call to Update, that way you avoid stubbing and the oddity of a person requiring a dependency to a "clock". – Wes Jan 04 '14 at 06:22
2

Your call:

p.Object.UpdatePerson("John", "Lennon");

calls a public virtual method UpdatePerson on your mock. Your mock has behavior Loose (also known as Default), and you did not Setup that virtual method.

Moq's behavior in that case is to just do nothing in its implementation (override) of UpdatePerson.

There are several ways you could change this.

  • You could remove virtual keyword from UpdatePerson method. Then Moq will not (and cannot) override its behavior.
  • Or you could actually Setup the virtual method with Moq before you call it. (Not useful in this case since it overrides the method you actually want to test.)
  • Or you could say p.CallBase = true; before you call the method. This works as follows (with Loose behavior): If a virtual member that was not setup is called, Moq will call the implementation of the base class.

This explains what you saw. I can agree with the advice Sergey Berezovskiy gives in his answer.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • Option 1 is good, but sadly it's incompatible with NHibernate, it will refuse to map a class with a non-virtual method in it. Guess a class can't serve two masters :D Or I can really try the third option, the **p.CallBase = true;**, but I concur with Sergey's answer; I should give more thoughts on behaviors, if it should be mocked or not – Hao Jan 01 '14 at 13:41
  • +1 Thanks for sharing something new I learned today. **p.CallBase = true;** works. But it's not a substitute for critical thinking, I should give more thoughts on what and what not to mock, and how to do effective unit testing – Hao Jan 01 '14 at 13:55