user10746369
user10746369

Reputation:

C# - Unit test view model's public getter without calling class constructor?

How can I call a C# property's public getter from a unit test without calling the class's constructor?

I am currently writing unit tests for a view model.

I am not testing private methods, only public methods.
I am testing any public getters and setters that implement custom logic or validation.

My class contains a private list of items.
My class contains a property which returns some of these items based on an item's property value.
My class contains a constructor which makes a call to a database.

I want to test that this getter returns those items correctly, because the getter is part of the class's public interface and should be tested.

My problem is that I want to somehow mock my class such that I can call the getter of the class property during a unit test. However, I don't want to call the constructor of the class as that would mean calling into the database and performing other work outside of the intended scope of the test.


Here's an example with monkeys and bananas:

public class Monkey {

    private List<Banana> bananas;

    public Monkey()
    {
        this.GetBananasFromDatabase()
    }

    public List<Banana> PeeledBananas {
        get {
            List<Banana> peeledBananas = new List<Banana>();
            foreach(Banana banana in this.bananas)
            {
                if(banana.IsPeeled)
                {
                    peeledBananas.Add(banana);
                }
            }
            return peeledBananas;
        }
    }

    private GetBananasFromDatabase()
    {
        this.bananas = Database.GetBananas();
    }
}

In this example, I want to test the getter for the PeeledBananas property.
I don't want to call the constructor, because I don't want to call the GetBananasFromDatabase() method.

The PeeledBananas getter is part of the public API of the monkey class, so it should be unit tested. But the database should not be called during the test.

I could create a new monkey mock where I can call the constructor anyway and just mock the behavior of the GetBananasFromDatabase() method, but I can't mock the GetBananasFromDatabase() method because it's private.

What's a good way to solve this?

Upvotes: 1

Views: 686

Answers (1)

user10746369
user10746369

Reputation:

Nkosi's comment provided a useful insight. My issue was that my code was tightly coupled due to the use of a concrete database type. By practicing polymorphism and inversion of control (IOC), I decided to pass a database interface to my constructor and call methods on that instead of calling methods on a concrete database.

Now when I want to unit test something, I can just pass in a fake database object into the monkey constructor and I don't have to touch the GetBananasFromDatabase() method.

Here's what the code looks like now:

public class Monkey {

    private List<Banana> bananas;
    private IDatabase database;

    public Monkey(IDatabase database)
    {
        this.database = database;
        this.GetBananasFromDatabase()
    }

    public List<Banana> PeeledBananas {
        get {
            List<Banana> peeledBananas = new List<Banana>();
            foreach(Banana banana in this.bananas)
            {
                if(banana.IsPeeled)
                {
                    peeledBananas.Add(banana);
                }
            }
            return peeledBananas;
        }
    }

    private GetInfoFromDatabase()
    {
        this.bananas = this.database.GetBananas();
    }
}

Now my monkey can get bananas from whatever database makes sense for the given context, and the monkey class is no longer tightly coupled to a specific IDatabase implementation.

When I want to use the monkey class in production code, I instantiate the class with some RealDatabase. when I want to unit test the monkey class, I instantiate the the class with some FakeDatabase. Both implement IDatabase and GetBananas(), so monkey works no matter what.

Thanks for the tip, Nkosi!

Upvotes: 2

Related Questions