5

I have a question about unit testing.

Say I have a controller with one create method which puts a new customer in the database:

//code a bit shortened
public actionresult Create(Formcollection formcollection){
    client c = nwe client();
    c.Name = formcollection["name"];
    ClientService.Save(c);
{

Clientservice would call a datalayer object and save it in the database.

What I do now is create a database testscript and set my database in a know condition before testing. So when I test this method in the unit test, I know that there must be one more client in the database, and what its name is.In short:

ClientController cc = new ClientController();
cc.Create(new FormCollection (){name="John"});
//i know i had 10 clients before
assert.areEqual(11, ClientService.GetNumberOfClients());
//the last inserted one is John
assert.areEqual("John", ClientService.GetAllClients()[10].Name);

So I've read that unit testing should not be hitting the database, I've set up an IOC for the database classes, but then what? I can create a fake database class, and make it do nothing.

But then of course my assertions will not work because if I say GetNumberOfClients() it will always return X because it has no interaction with the fake database class used in the Create Method.

I can also create a List of Clients in the fake database class, but as there will be two different instances created (one in the controller action and one in the unit test), they will have no interaction.

What is the way to make this unit test work without a database?

EDIT: The clientservice doesn't connect directly to the DB. It calls a ClientDataClass which will connect to the database. So the ClientDatabaseClass will be replaced with a fake

Michel
  • 21,571
  • 43
  • 141
  • 223

4 Answers4

5

In this particular case you are testing controller in isolation from database. ClientService is abstraction over the database, and should be replaced by test double. You injected fake into controller, but still assert real implementation. This makes no sense.

Assert the same object, which was injected into controller.

 interface IClientService 
    {
      public void GetNumberOfClients();
      public IList<Client> GetAllClients();
      public void Insert(Client client);
    }

Fake service implementation:

   class FakeClientService : IClientService
   {
     private IList<CLient> rows = new List<CLient>();

     public void GetNumberOfClients()
     { 
       return list.Count;
     }

     public IList<Client> GetAllClients()
     {
       return list;
     }

     public void Insert(Client client)
     {
       client.Add(client);
     }
   }

Test:

[Test]
public void ClientIsInserted()
{
  ClientController cc = new ClientController();
  FakeClientService fakeService = new FakeClientService();

  cc.ClientService = fakeService;

  cc.Create(new FormCollection (){name="John"});

  assert.areEqual(1, fakeService.GetNumberOfClients());
  assert.areEqual("John", fakeService.GetAllClients()[0].Name);
}

If you want to check how controller and service work together - create fake for ClientDatabaseClass. It would be like:

[Test]
public void ClientIsInserted()
{
  ClientController cc = new ClientController();
  IClientDatabaseClass databaseFake = new ClientDatabaseClassFake();

  ClientService service= new ClientService();

  service.Database = databaseFake;
  cc.ClientService = service;

  cc.Create(new FormCollection (){name="John"});

  assert.areEqual(1, service.GetNumberOfClients());
  assert.areEqual("John", service.GetAllClients()[0].Name);
}
Yauheni Sivukha
  • 2,432
  • 19
  • 21
  • Sorry, wasn't realy clear about that: The clientservice doesn't connect directly to the DB. It calls a ClientDataClass which will connect to the database. So the ClientDatabaseClass will be replaced with a fake – Michel Apr 22 '10 at 07:43
  • Michel, you need to decide - what do you want to test? If you want to test logic in controller, then the best option would be to replace it's neighbor classes by test doubles. If you want to check how controller and service work together - create fake for ClientDatabaseClass. – Yauheni Sivukha Apr 22 '10 at 07:59
  • I want to test the controller. So i want to check if validations go well and if the new client is saved to the database. So to be clear: i'm faking the databaseclasses (where the object is saved to the database) . Say during my test i create a list with clients and i add one, and during my assert in my unit test i want to check if the client was added, i have a new instance of the database class so the original list, with the new client added, is already gone. Do you save the original list on disk or something? – Michel Apr 22 '10 at 08:47
  • No need to save list on list - just use one instance of fake both for saving and asserting. If you can not inject the same object everywhere in your system - then it is something wrong with the way you create object. By the way how do you call service? Is it a static call? – Yauheni Sivukha Apr 22 '10 at 11:24
  • No, it is not a static call. But let me try to think what you say: should i say to my IOcontainer: only create ONE databaseclass, and not a new one for every call for a new one? And in that case, i'm always using the same instance? And in production, i can of course configure the ioc container to create a new one per request? – Michel Apr 22 '10 at 12:50
  • Yes, this is the approach I always use in my unit tests. In such configuration singleton imitates persistence storage. – Yauheni Sivukha Apr 22 '10 at 14:09
2

This is where unit-testing, in my opinion, becomes hard.

The way I have done it in the past is to effectively abstract the whole database away. How you do that will depend on what you're trying to do because databases are obviously fairly versatile. In your specific example something like the following:

public interface IDatabase<T>
{
    void Create(T value);
    int Count { get; }
    T[] All { get; }
}

You then implement this interface using some simple in-memory container and then implement it again using the real database accessors. The in-memory container is often referred to as a 'test-double'.

This gives you your separation that allows you to continue to unit-test your controller code without needing to access a database.

Of course you still have the problem of how you unit-test your database access layer. For this I might be tempted to use a real database or make it tested by a suite of integration tests.

Steve Knight
  • 807
  • 7
  • 12
  • Thanks, but how would i implement this in-memory container? The problem here is that two instances of the database layer class are instantiated (one in the controller, one in the test) so they have to share the data somehow. – Michel Apr 22 '10 at 07:39
  • Oh yes that is a problem. The way I have done this in the past is to have a further class called 'TypeRegistry' which allows you to associate a Type with an instance. Then in your real code you set up this registry with the real access layer. In the test code you setup the registry with the in-memory container. You can do this in a variety of ways though. This is one of the purposes of the spring frameowrk for example. – Steve Knight Apr 22 '10 at 09:07
1

Perhaps you could make your fake DB class Serialiseable and load it up from one location each time. That would allow you to persist the data in it, so it would behave as if it was a database, without really being one.

Matt Ellen
  • 9,933
  • 4
  • 61
  • 80
  • Won't i be building a (very simple) database this way, so replacing a normal database with another? – Michel Apr 22 '10 at 07:44
1

Use dependency injection, and instead of hitting your database, create a repository and use that (at least that's how I do it when it comes to unit testing)

edit: This is pretty much the same answer as Steve Knight's, all be it much shorter :)

Nathan
  • 1,699
  • 3
  • 18
  • 25
  • How would that repository look like? Because in it's nature it must do the same thing as a database, because it must keep data over time. Won't i be building a (very simple) database, so replacing a normal database with another? – Michel Apr 22 '10 at 07:43
  • Yauheni Sivukha posted exactly (really, for the full 100%) what I've ment. You set up an interface to provide the posibility of dependency injection, and create a class (using lists/dictionaries/we.) that acts as the database layer. – Nathan Apr 22 '10 at 07:52
  • I'm not an entity framework expert or anything, but you may have a point that when the Database Structure comes rather complex to simulate with static lists and so on you might be better of calling entity framework, but my experience is that using static lists and dictionaries is a quick, non time-consuming solution for running unit tests. – Nathan Apr 22 '10 at 08:33
  • @Arsenal How do you maintain data between the different instances of the database classes. Say during my test i create a list with clients and i add one, and during my assert in my unit test i want to check if the client was added, i have a new instance of the database class so the original list, with the new client added, is already gone. Do you save the original list on disk or something? – Michel Apr 22 '10 at 08:45
  • erm, not 100% sure if I understand your question, but if you're using static lists, you shouldn't worry about losing your data over multiple database class instances? Also, I prefer applying the Singleton Design to implement those Repositories (fake DB classes) as I don't really see the need to have multiple classes. – Nathan Apr 22 '10 at 09:56