Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing and nhibernate?

I am wondering how to get around this. I am using nhibernate and fluent.

I have a domain class like this

public class User
{
   public virtual int UserId {get; private set;}
}

this seems to be the convention when doing nhibernate as it stops people from setting and id as it is auto generated.

Now the problem comes when I am unit testing.

I have all my nhibernate code in a repo that I mock out so I am only testing my service layer. The problem comes when this happens.

User user = repo.GetUser(email);

this should return a user object.

So I want to use moq to do this

repo.Setup(x => x.GetUser(It.IsAny<string>())).Return(/* UserObject here */)

now here is the problem

I need to make that User object and put it in the Return part.

So I would do something like

User user =  new User()
{
   UserId = 10,
}

But this is where the problem lies I need to set the Id because I actually use it later on to do some linq on some collections(in the service layer as it is not hitting my db so it should not be in my repo) so I need to have it set but I can't set it because it is a private set.

So what should I do? Should I just remove the private or is there some other way?

like image 499
chobo2 Avatar asked Feb 01 '11 22:02

chobo2


2 Answers

You can have the fake Repository object return a fake User object:

var stubUser = new Mock<User>();
stubUser.Setup(s => s.UserId).Returns(10);

var stubRepo = new Mock<IUserRepository>();
stubRepo.Setup(s => s.GetUser(It.IsAny<string>())).Return(stubUser);

There are a couple of things to observe here:

  1. Moq can only fake members of concrete classes if they are marked as virtual. This may not be applicable in some scenarios, in which case the only way to fake an object through Moq is having it implement an interface.
    In this case, however, the solution works nicely because NHibernate already imposes the same requirement on the properties of the User class in order to do lazy loading.
  2. Having fake objects returning other fakes may sometimes lead to over specified unit tests. In these situations, the construction of rich object models made up of stubs and mocks grows to the point where it becomes difficult to determine what exactly is being tested, making the test itself unreadable and hard to maintain. It is a perfectly fine unit testing practice, to be clear, but it must be used consciously.

Related resources:

  • Over Specification in Tests
like image 176
Enrico Campidoglio Avatar answered Oct 17 '22 20:10

Enrico Campidoglio


Enrico's answer is spot on for unit testing. I offer another solution because this problem crops up in other circumstances too, where you might not want to use Moq. I regularly use this technique in production code where the common usage pattern is for a class member to be read-only, but certain other classes need to modify it. One example might be a status field, which is normally read-only and should only be set by a state machine or business logic class.

Basically you provide access to the private member through a static nested class that contains a method to set the property. An example is worth a thousand words:

public class User {
    public int Id { get; private set; }

    public static class Reveal {
        public static void SetId(User user, int id) {
            user.Id = id;
        }
    }
}

You use it like this:

User user = new User();
User.Reveal.SetId(user, 43);

Of course, this then enables anyone to set the property value almost as easily as if you had provided a public setter. But there are some advantages with this technique:

  • no Intellisense prompting for the property setter or a SetId() method
  • programmers must explicitly use weird syntax to set the property with the Reveal class, thereby prompting them that they should probably not be doing this
  • you can easily perform static analysis on usages of the Reveal class to see what code is bypassing the standard access patterns

If you are only looking to modify a private property for unit testing purposes, and you are able to Moq the object, then I would still recommend Enrico's suggestion; but you might find this technique useful from time to time.

like image 2
Rich Tebb Avatar answered Oct 17 '22 22:10

Rich Tebb