2

I was wondering what I should test and how I should test an IRepository.

At the moment I created a MemoryRepository in my domain and I am testing that with fake data. I am not sure if this is correct though. Feels a little weird to first create some data and then test to see if the repository returns it correctly.

This is what my MemoryRepository looks like:

public class MemoryRepositoryUser : IRepositoryUser
{
    List<User> fakeUsers;       

    public MemoryRepositoryUser(List<User> fakeUsers)
    {
        this.fakeUsers = fakeUsers;
    }

    public IEnumerable<User> GetAll()
    {
        return fakeUsers;
    }

    public User GetById(string id)
    {
        return GetAll().Where(u => u.Username == id).Single();
    }
}

These are some tests I wrote:

[TestFixture]
class TestMemoryRepositoryUser
{
    private MemoryRepositoryUser repository;

    public TestMemoryRepositoryUser(){
        repository = new MemoryRepositoryUser(FakeData.GetFakeUsers());
    }

    [Test]
    public void Get_All_Users()
    {
        var Users = repository.GetAll();
        Assert.IsNotNull(Users);
        Assert.IsInstanceOf(typeof(IEnumerable<User>), Users);
        Assert.AreEqual(3, Users.Count());
    }

    [Test]
    public void Get_User_By_ID()
    {
        var Username = "Bob";
        var User = repository.GetById(Username);
        Assert.IsNotNull(User);
        Assert.AreEqual(Username, User.Username);
    }
}

I am pretty new to testing code and I mostly have problems with what I should test. I guess testing a MemoryRepository helps me to create all the features I need in the interface without having to talk to a database?

Pickels
  • 33,902
  • 26
  • 118
  • 178

3 Answers3

5

Typically repository tests are integration tests and they do talk to the database. As you've noticed, there's little point to testing a repository with fake data.

It's usually the repository that's mocked to test other classes. By mocking every dependency except the class under test, you can isolate the functions that you are testing. The benefit of declaring interfaces for repositories is that it allows them to be easily mocked and used in unit tests of other code.

Jamie Ide
  • 48,427
  • 16
  • 81
  • 117
3

If you plan to use your MemoryRepositoryUser only as a stub to test the behavior in the repository's client, then I recommend you don't test MemoryRepositoryUser itself for now. (If you want to test a test double, then it has become too complicated.) Instead, focus your energy on testing or test-driving the production implementation of IRepositoryUser.

On the other hand, remember that your production implementation of IRepositoryUser, which I'll call AdoBasedRepositoryUser (for the example where you implement to use ADO), needs to behave the same way as your stub. If not, then the tests for the repository's client might assume the wrong behavior in IRepositoryUser. You might consider some tests for that.

For example, when you test AdoBasedRepositoryUser, you will check GetById() by writing a test similar to this:

in AdoBasedRepositoryUserTest...
[Test]
GetById_RecordFound() {
    insert record with ID 762 directly into the USERS table
    User expected = User with ID 762 and the mandatory properties set
    User actual = new AdoBasedRepositoryUser().GetById(762);
    Assert.AreEqual(expected, actual);    // Implement User.Equals() to compare the values
}

You will want to verify that MemoryRepositoryUser also passes this test, so that you can use that to stub IRepositoryUser in tests for its client.

in MemoryRepositoryUserTest...
[Test]
GetById_RecordFound() {
    MemoryRepositoryUser repository = new MemoryRepositoryUser();
    User expected = User with ID 762 and the mandatory properties set
    repository.Add(expected);
    User actual = repository.GetById(762);
    Assert.AreEquals(expected, actual);
}

As long as you have tests in MemoryRepositoryUserTest that match the tests in AdoBasedRepositoryUser, then you can feel confident that your stub matches the production behavior enough to safely use the stub when testing the Services that use this Repository.

After you've done this a few times, you might be ready to look at Contract Tests. (Search the web.)

One last thing: I would name your repository interface IUserRepository instead of IRepositoryUser.

J. B. Rainsberger
  • 1,193
  • 9
  • 23
  • 1
    Not sure if you're thoughts are the same nearly 11 years after this answer, but I'll ask anyways :). Two related questions. 1) What would one of the tests in your example look like for testing the behaviour when the backend server is down, and how would you set that condition up in tests? 2) And related, when using the `MemoryRepositoryUserTest` in your *other* tests, how would you enable or trigger the 'backend server is down' behaviour? A boolean field on `MemoryRepositoryUserTest` that you can toggle on/off (maybe `isBackendDown`)? – jmrah Dec 13 '20 at 12:18
  • @jrahhali I updated the answer. Not much changed. – J. B. Rainsberger Dec 14 '20 at 14:23
  • 1
    @jrahhali To test the (production) Repository implementation when "the back end" is down, I assume that the Repository implementation X has a reference to a Gateway to the back end Y. I can use the Crash Test Dummy pattern on Y: stub Y to fail however the real back end fails, then check that X handles the failure gracefully. Maybe we need to hide Y behind an interface to do this. Maybe we need Contract Tests for Y to show that Y signals failure by throwing exactly the right exception. – J. B. Rainsberger Dec 14 '20 at 14:25
  • @jrahhali I don't think I would want to trigger the "back end server is down" behavior on the In-Memory implementation of the Repository. What for? The In-Memory implementation simply can't fail that way. I wouldn't expect ever to connect the In-Memory Repository with the "back end". Why would you want to do that? – J. B. Rainsberger Dec 14 '20 at 14:27
  • In response to your *latest* comment, perhaps a example might clear up my question. And sorry for the my long comment that follows, but I want to make sure I fully understand the idea your presenting here because it sounds really helpful. So, extending from your example in your answer: Say `AdoBasedRepositoryUser.GetById()` throws a 'FooException` when the "backend server is down". Even though "the backend server is down" behaviour cannot happen in the in-memory version, isn't this throwing of `FooException` part of the behaviour you would want to mimic for `MemoryRepositoryUser.GetById()`? – jmrah Dec 14 '20 at 16:24
  • If yes, if I was testing a Service that was used `MemoryRepositoryUser`, and I wanted to test a `FooException` being thrown when `MemoryRepositoryUser.GetById()` was called, how would you set that condition up? Would you use a mocking library to stub `GetById()` on the `MemoryRepositoryUser` class, or other? – jmrah Dec 14 '20 at 16:24
  • 1
    @jrahhali The In-Memory Repository's `GetById()` _is allowed_ to throw `FooException`, but it is not _required_ to do so. A `UserRepository`, in general, might throw `FooException` from `GetById()`. Any implementation that _does_ throw it needs to throw it "for the agreed-upon reasons". That's part of the contract of `GetById()` for the `UserRepository` interface. – J. B. Rainsberger Dec 15 '20 at 23:13
  • @jrahhali A Service (client) of `UserRepository` needs to be prepared that `GetById()` might throw `FooException`, but if we connect that Service to an In-Memory Repository, then that Service's handler for `FooException` will never execute. That's OK. There is no reason ever to test that the Service receives `FooException` from the In-Memory Repository, because _that can never happen_. Instead, we test what happens if a generic `UserRepository.GetById()` throws a `FooException`. For this we can use a lambda expression or a test double/mock object. – J. B. Rainsberger Dec 15 '20 at 23:15
  • @jrahhali You probably can't use an In-Memory Repository to simulate a `FooException`. You could create an anonymous subclass that overrides a method and throws `FooException`. If you're going to do that, then it's simpler to create an anonymous implementation of the interface directly. :) – J. B. Rainsberger Dec 15 '20 at 23:21
  • 1
    Thanks for your thoughtful responses. You've answered my questions and have given me some ideas to mull over. – jmrah Dec 15 '20 at 23:42
1

One thing I notice, which may not be related to your question, is the state of repository should be independent of all the other tests that run. In this case, the same instance is acted upon by each test.

Consider what happens when you write a Delete_user test. It shouldn't matter if that test runs before Get_All_Users. I would new up a new MemoryRepositoryUser in each test. This test smell can exist in other tests and isn't specific to if you should test the fake or not.

Typically I would use a sub/mock in favor of a fake. As Jamie mentioned, the interface allows you to easily mock your dependencies.

Mark
  • 9,966
  • 7
  • 37
  • 39