Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the way to go to fake my database layer in a unit test?

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

like image 589
Michel Avatar asked Apr 22 '10 07:04

Michel


People also ask

Should I mock the database in unit tests?

Unit tests are incredibly important to us as developers because they allow us to demonstrate the correctness of the code we've written. More importantly, unit tests allow us to make updates to our code base with confidence that we haven't broken anything.

Can you unit test a database?

SQL Server supports database unit testing as a part of its feature suite. You can create a test project and add a SQL Server unit test directly that you can then work on. SQL Test is another tool where the database unit tests run in transactions. It later rolls back any changes, so you won't need any cleanup code.


4 Answers

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);
}
like image 192
Yauheni Sivukha Avatar answered Oct 21 '22 07:10

Yauheni Sivukha


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.

like image 43
Steve Knight Avatar answered Oct 21 '22 09:10

Steve Knight


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.

like image 22
Matt Ellen Avatar answered Oct 21 '22 07:10

Matt Ellen


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 :)

like image 36
Nathan Avatar answered Oct 21 '22 07:10

Nathan