10

I'm trying to test an Order entity method called AddItem and I'm trying to make sure that duplicate items cannot be added. Here is some example code:

[Test]
public void ItemCannotBeAddedTwiceToOrder()
{
    Order o = new Order();
    Item i = new Item("Bike");

    o.AddItem(i);
    o.AddItem(i);

    Assert.AreEqual(o.ItemCount, 1, "A duplicate item was added.");
}

public void AddItem(Item newItem)
{
    if(!CheckForDuplicateItem(newItem))
       _items.Add(newItem);
}

public bool CheckForDuplicateItem(Item newItem)
{
    foreach(Item i in _items)
    {
        if(i.Id == newItem.Id)
          return true;
    }

    return false;
}

So here is my problem: how do I set the new Item's private setter Id in the test method so the CheckForDuplicateItem method will work? I don't want to make that member public for good coding practices, I guess. Am I just being stupid and need to make the entity Item have a public Id setter? Or do I need to use reflection? Thanks

Note - I'm using NHibernate for persistence

5 Answers 5

20

I usually use reflection for this purpose. Something like this will work:

typeof(Item).GetProperty(nameof(Item.Id)).SetValue(i, 1, null);

where 1 is the id that you want to set for the newItem instance.

In my experience, you'll rarely need to set the Id, so it's better just to leave the setter private. In the few cases that you do need to set the Id for testing purposes, simply use Reflection.

Sign up to request clarification or add additional context in comments.

1 Comment

Great solution, it can simulate setting ef core navigation properties that have private/no setter in unit tests of domain entities
7

Since you are checking the behavior of your Order you can use mock objects as its items. Using mock objects you can define your assertions of what's going to happen to your mock objects and test them too. In this case you can define two mock objects for each of items and expect that their id getter will be called and will return a unique value.Then you can test the Order behavior and check to see if id getter of item is called as you expected. I recommend using Rhino Mocks by Ayende

1 Comment

Good point. I'll look into mocking. I've heard a lot of good things about Rhino Mocks.
7

While Praveen's answer is correct and without doubt serves for the single usage, it misses some type safety to use this in tests over a strong domain model over and over. Therefore I wrapped it in an extension method that allows you this type safe call to set a value:

var classWithPrivateSetters= new ClassWithPrivateSetters();
classWithPrivateSetters.SetPrivate(cwps => cwps.Number, 42);

Drop this into your test assembly and you are good to go

public static class PrivateSetterCaller
{
    public static void SetPrivate<T,TValue>(this T instance, Expression<Func<T,TValue>> propertyExpression, TValue value)
    {
        instance.GetType().GetProperty(GetName(propertyExpression)).SetValue(instance, value, null);
    }

    private static string GetName<T, TValue>(Expression<Func<T, TValue>> exp)
    {
        MemberExpression body = exp.Body as MemberExpression;

        if (body == null)
        {
            UnaryExpression ubody = (UnaryExpression)exp.Body;
            body = ubody.Operand as MemberExpression;
        }

        return body.Member.Name;
    }
}

Comments

0

Another solution is to make private members accessible by deriving from the class and exposing the member in the derived class. This is quite a lot overhead for testing and Visual Studio has only build-in support for private methods.

Comments

0

I think you might be missing the point here. Are you avoiding multiple additions because you don't want multiple calls to the DB? I think NHibernate gives you that for free. Alternatively, should you be using a Set? What are the implications for the caller that an item might or might not be added?

If there's no persistence issue, you could just add two distinct items with the same ID and confirm that you only have the first one. There must be some way of detecting which items are in the order, or there would be no point in adding them...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.