FakeItEasy to test domain services + UnitOfWork

934 views Asked by At

I started doing some experimentation with unit testing so that we can include them in our domain layer. However i dont know if I'm following the right path, thus i'm going to explain what i'm currently doing to see if i'm on the right track. Basically the architecture is like the following there is Domain Layer containing domain models and domain services (ex. User class and UserService class). Then Domain layer communicates with the DAL which implements the Generic Repository pattern together with the Unit of Work. Each domain service class in it's constructor accepts an IUnitOfWork interface, like the following:

    public class UserService: IUserService
    {
        private readonly IUnitOfWork _unitOfWork;

        public UserService(IUnitOfWork unitOfwork)
        {
            this._unitOfWork = unitOfwork;
        }

    }

In order to creat the unit tests, i decided to go with FakeItEasy framework. So in a UserServiceTest class i did the following:-

  private IUserService _userService;
    private const int userID = 2013;

    [TestInitialize]
    public void Initialize()
    {
    _userService = A.Fake<IUserService>();

        A.CallTo(() => _userService.GetUserById(userID)).Returns(new User
            {
                UserID = userID,
                RegistrationDate = DateTime.Now,
            });
    }


    [TestMethod]
    public void GetUserByID()
    {
        var user = _userService.GetUserById(userID);
        Assert.IsInstanceOfType(user, typeof(Domain.User));
        Assert.AreEqual(userID, user.userID);
     }

When I run the tests, they pass. Is it the correct way of implementing unit tests? Before I was trying a different approach however FakeItEasy was failing with a ProxyGenerator exception. What i was doing is this:-

 [TestInitialize]
 public void Initialize()
 {
_unitOfWork = A.Fake<IUnitOfWork>();

A.CallTo(() => _unitOfWork.UserRepository.FindById(userID)).Returns(new UserDto
    {
        UserID = userID,
        RegistrationDate = DateTime.Now,
    });


AutoMapper.Mapper.CreateMap<UserDto, User();
 }

 [TestMethod]
 public void GetUserByID()
 {
     var userService = new UserService(_unitOfWork);
     var user = userService.GetUserById(userID);
     Assert.IsInstanceOfType(user, typeof(Domain.User));
     Assert.AreEqual(userID, user.userID);
 }

And this was throwing the below exception:-

Result Message: 
Initialization method Initialize threw exception. System.ArgumentNullException: System.ArgumentNullException: Value cannot be null.
Parameter name: callTarget.
Result StackTrace:  
at FakeItEasy.Creation.ProxyGeneratorSelector.MethodCanBeInterceptedOnInstance(MethodInfo method, Object callTarget, String& failReason)
   at FakeItEasy.Configuration.DefaultInterceptionAsserter.AssertThatMethodCanBeInterceptedOnInstance(MethodInfo method, Object callTarget)
   at FakeItEasy.Configuration.FakeConfigurationManager.AssertThatMemberCanBeIntercepted(LambdaExpression callSpecification)
   at FakeItEasy.Configuration.FakeConfigurationManager.CallTo[T](Expression`1 callSpecification)
   at FakeItEasy.A.CallTo[T](Expression`1 callSpecification)

Any feedback would be greatly appreciated. Thanks!

1

There are 1 answers

0
Blair Conrad On

I think your original (second, in the question) test was failing because _unitOfWork.UserRepository is coming back as null in Initialize. Normally FakeItEasy will create an fake object when chained properties are used, but I'm guessing (I have to guess because I don't know anything about the type of UserRepository) that UserRepository's type is not fakeable. In that case, you'd get a null back from _unitOfWork.UserRepository.

Let me jump back to your second test (which was first in your question), then we'll return to what I think you might want to do here.

Looking at your test,

var user = _userService.GetUserById(userID);
Assert.IsInstanceOfType(user, typeof(Domain.User));
Assert.AreEqual(userID, user.userID);

I see a flaw. You're invoking a method on _userService directly, but _userService is a fake object, so the test doesn't actually involve any of the production code. It's really only exercising FakeItEasy.

I think what we want is sort of a blended approach - something that will exercise the code in a real UserService, without worrying about UserRepository. Maybe something similar to (and I'm not using a compiler here, and don't know what methods are on IUnitOfWork so take this with a grain of salt)

[TestInitialize]
public void Initialize()
{
    _unitOfWork = A.Fake<IUnitOfWork>();
    A.CallTo(() => _unitOfWork.GetUserById(userID))
        .Returns(new User
        {
            UserID = userID,
            RegistrationDate = DateTime.Now,
        });
}

[TestMethod]
public void GetUserByID()
{
    var userService = new UserService(_unitOfWork);
    var user = userService.GetUserById(userID);
    Assert.IsInstanceOfType(user, typeof(Domain.User));
    Assert.AreEqual(userID, user.userID);
}

Or, if there's nothing useful on IUnitOfWork except for UserRepository, then I think the next step would be to investigate why the type of UserRepository wasn't fakeable (if my guess was right) - is it sealed? Does it lack appropriate and accessible constructors?