I've heard somewhere that mocking an entity is a BAD thing, and you should only mock services (even if we're not doing full-blown DDD, but "sorta" DDD).
So, given the following typical story, how do you write a test for it?
- If a customer has a Preferred status, she should be notified when Stuff happens.
Preferred status is something dependent on other things, like, the customer has recently purchased 10 Widgets, or his name starts with "A", etc. Anyway, let's suppose we have already implemented this functionality. How do I write a test for the above story? Specifically, I'm interested in how testability requirement would affect my design in this case.
- I can use some advanced mock framework (lik开发者_JAVA技巧e Isolator) which lets me mock non-virtual properties. No correlation between testability and design, no problem.
- I can make the IsPreferred property virtual and mock it (stub actually). Don't know why, but that feels dirty. See the question at the top of the post. Also, not sure how it improves my design. 2a. Hide the entity behind the ICustomer interface and mock it. Totally uncool.
- I can make it a read-write property, but that would be a very bad design decision.
How do you handle such stories?
I've read on a few books / blogposts a similar thing that suggested not to mock Entities or objects from the domain model. As far as I remember the message was don't mock data, mock behaviour. (I probably read this in the book XUnit Test Patterns or GOOS).
Personally, I think that if your entity needs a lot of setup, it's valid to mock it. Let me give you an example, imagine you have Brands, Products, Prices and StockAvailability. A Brand has Producs, Products have Prices and Stock. The Class Under Test invokes Brand.IsAvailable() which in turns checks that there's at least one product with a valid price and in stock (that's quite a lot of logic). So if you're trying to unit test the CUT, testing all the Brand.IsAvailable logic is well out of scope, so it would make sense to mock that method.
If it's easy to set up the entity, then use the entity, for example if you have a User and it has a property called active
, you could create a user and set that property in your test as the cost of building a fake user and assigning that property is probably the same as mocking the method call, and it's (in my view) clearer.
I'm not a c# expert, but I've read that Isolator is one of the best frameworks for .net, so I would do what you mentioned on 1.
I would't put too much logic into entities. They should care about their own state, eg. care about consistency of their own fields. But they shouldn't care about consistency within the whole system. Your example of "user.AddOrder" that is setting a user state is going much too far IMHO.
You also loose reusability of your entities if they do everything. If you get additional requirements (eg. additional situations like import of data, new kind of order, new kind of rule), your entity model gets in the way, it is not flexible enough.
This lack of flexibility shows up in the unit tests. When you isolate classes in the unit tests, it gets clear what's actually encapsulated and can't be separated anymore. If you get a problem with that, your encapsulation is most probably not very useful. So unit tests are a great proof of reusability.
Example (pseudocode, don't mind if I miss the point of your application, it's just to show where logic belongs to):
class User
{
AccountState State { get; }
void MakeTopSeller()
}
class Order
{
decimal GetTotal();
User Salesman { get; }
}
class OrderService
{
void AddOrder()
{
// ....
if (order.GetTotal() > ToSellerAmount)
{
order.Salesman.MakeTopSeller();
}
}
}
Edit: If you still think that your approach is appropriate (I mean I can't decide from what I know about it), you don't need to change it. But if your entity takes another entity to set a state, and you need that state in the test, you need to do all this in the test: create an order and add it to the user.
There is another way to split things off. You could put the rule into a separate class, eg. using the strategy pattern. It is usually hard to set strategies on entities, because the underlying database layer needs to inject them.
精彩评论