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?
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:
- 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 theUser
class in order to do lazy loading. - 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
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.
Another alternative if you prefer not to mock your entity classes is to set the private/protected ID using reflection.
Yes, I know that this is usually not looked upon very favourably, and often cited as a sign of poor design somewhere. But in this case, having a protected ID on your NHibernate entities is the standard paradigm, so it seems a quite reasonable solution.
We can try to implement it nicely at least. In my case, 95% of my entities all use a single Guid as the unique identifier, with just a few using an integer. Therefore our entity classes usually implement a very simple HasID
interface:
public interface IHasID<T>
{
T ID { get; }
}
In an actual entity class, we might implement it like this:
public class User : IHasID<Guid>
{
Guid ID { get; protected set; }
}
This ID is mapped to NHibernate as a primary key in the usual manner.
To the setting of this in our unit tests, we can use this interface to provide a handy extension method:
public static T WithID<T, K>(this T o, K id) where T : class, IHasID<K>
{
if (o == null) return o;
o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { id });
return o;
}
We don't have to have the HasID interface to do this, but it means we can skip a bit of extra code - for example we don't need to check if the ID is actually supported or not.
The extension method also returns the original object, so in usage I usually just chain it onto the end of the constructor:
var testUser = new User("Test User").WithID(new Guid("DC1BA89C-9DB2-48ac-8CE2-E61360970DF7"));
Or actually, since for Guids I don't care what the ID actually is, I have another extension method:
public static T WithNewGuid<T>(this T o) where T : class, IHasID<Guid>
{
if (o == null) return o;
o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { Guid.NewGuid() });
return o;
}
And in usage:
var testUser = new User("Test User").WithNewGuid();
Instead of trying to mock out your repository, I'd suggest you try to use an in-memory SQLite database for testing. It will give you the speed you're looking for and it will make things a lot easier too. If you want to see a working sample you can have a look at one of my GitHub projects: https://github.com/dlidstrom/GridBook.
精彩评论