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 wo开发者_运维问答rk 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
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);
}
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.
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.
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 :)
精彩评论