Reputation: 447
I'm getting my feet wet with unit testing (TDD). I have a basic repository pattern I'm looking to test and I'm not really sure I'm doing things correctly. At this stage I'm testing my domain and not worrying about controllers and views. To keep it simple here is a demo project.
Class
public class Person
{
public int PersonID { get; set; }
public string Name{ get; set; }
}
Interface
public interface IPersonRepository
{
int Add(Person person);
}
Concrete
public class PersonnRepository : IPersonRepository
{
DBContext ctx = new DBContext();
public int Add(Person person)
{
// New entity
ctx.People.Add(person);
ctx.SaveChanges();
return person.id;
}
}
I've added NUnit and MOQ to my test project and want to know how to properly test the functionality.
I'm not sure it's right but after reading some blogs I ended up creating a FakeRepository, however if I test based on this, how is that validating my actual interface?
public class FakePersonRepository
{
Dictionary<int, Person> People = new Dictionary<int, Person>();
public int Add(Person person)
{
int id = People.Count + 1;
People.Add(id, person);
return id;
}
}
then tested with
[Test]
public void Creating_A_Person_Should_Return_The_ID ()
{
FakePersonRepository repository = new FakePersonRepository();
int id = repository.Add(new Person { Name = "Some Name" });
Assert.IsNotNull(id);
}
Am I anywhere close to testing in the correct manor?
I'd like to test things like not passing a name causes error etc in the future.
Upvotes: 0
Views: 352
Reputation: 14661
I'd personally look at writing an "integration test" for this, i.e. one that hits a real (ish) database as your data access layer should not contain any logic which makes testing in isolation worthwhile.
In this case you will require a database up and running. This could be a developer database already set up somewhere, or an in-memory database started as part of the tests arrange.
The reason for this is that I find (pure) unit tests of the DAL generally end up as proof you can use a mock framework and little more and don't end up giving you much confidence in your code.
If you are completely new to unit tests and don't have colleges on hand to help set up the environment required for DAL testing you then I'd recommend you leave testing the DAL for now and concentrate on the business logic as this is where you will get the biggest bang for your buck and will make it easier see how the tests will help you.
Upvotes: 0
Reputation: 5551
You need to make your DBContext injectable by extracting an interface for it:
public interface IDBContext{
IList<Person> People {get;} // I'm guessing at the types
void SaveChanges();
// etc.
}
Then inject that into your concrete class:
public class PersonRepository : IPersonRepository
{
IDBContext ctx;
public PersonRepository(IDBContext db) {
ctx = db;
}
public int Add(Person person)
{
// New entity
ctx.People.Add(person);
ctx.SaveChanges();
return person.id;
}
}
Your test would then look like:
[Test]
public void Creating_A_Person_Should_Return_The_ID ()
{
Mock<IDBContext> mockDbContext = new Mock<IDBContext>();
// Setup whatever mock values/callbacks you need
PersonRepository repository = new PersonRepository(mockDbContext.Object);
int id = repository.Add(new Person { Name = "Some Name" });
Assert.IsNotNull(id);
// verify that expected calls are made against your mock
mockDbContext.Verify( db => db.SaveChanges(), Times.Once());
//...
}
Upvotes: 0
Reputation: 1038850
Am I anywhere close to testing in the correct manor?
I am afraid that you are not. The idea of having an interface is that it allows you to decouple other code that uses a repository such your controller and be able to unit test it in isolation. So let's suppose that you have the following controller that you want to unit test:
public class PersonController : Controller
{
private readonly IPersonRepository _repo;
public PersonController(IPersonRepository repo)
{
_repo = repo;
}
[HttpPost]
public ActionResult Create(Person p)
{
if (!ModelState.IsValid)
{
return View(p);
}
var id = _repo.Add(p);
return Json(new { id = id });
}
}
Notice how the controller doesn't depend on a specific repository implementation. All needs is that this repository implements the given contract. Now we could use a mocking framework such as Moq in the unit test to provide a fake repository and make it behave as we like in order to test the 2 possible paths in the Create
action:
[TestMethod]
public void PersonsController_Create_Action_Should_Return_View_And_Not_Call_Repository_If_ModelState_Is_Invalid()
{
// arrange
var fakeRepo = new Mock<IPersonRepository>();
var sut = new PersonController(fakeRepo.Object);
var p = new Person();
sut.ModelState.AddModelError("Name", "The name cannot be empty");
fakeRepo.Setup(x => x.Add(p)).Throws(new Exception("Shouldn't be called."));
// act
var actual = sut.Create(p);
// assert
Assert.IsInstanceOfType(actual, typeof(ViewResult));
}
[TestMethod]
public void PersonsController_Create_Action_Call_Repository()
{
// arrange
var fakeRepo = new Mock<IPersonRepository>();
var sut = new PersonController(fakeRepo.Object);
var p = new Person();
fakeRepo.Setup(x => x.Add(p)).Returns(5).Verifiable();
// act
var actual = sut.Create(p);
// assert
Assert.IsInstanceOfType(actual, typeof(JsonResult));
var jsonResult = (JsonResult)actual;
var data = new RouteValueDictionary(jsonResult.Data);
Assert.AreEqual(5, data["id"]);
fakeRepo.Verify();
}
Upvotes: 4