Reputation: 1279
I've been reading about TDD and I've seen a lot of posts about not to do any database transaction because "single, isolated block of code with no dependencies".
So now I have a little bit of dilemma - I want to be able to test if my service layer method called AddNewStudent
actually works. This method goes into my DbContext
and then add a new record into the database. If database operations aren't recommended for TDD then how else can I test AddNewStudent
method aside from just testing my application on a browser?
public class StudentManager : ManagerBase
{
internal StudentManager() { }
public Student AddNewStudent(string fName, string lName, DateTime dob)
{
// Create a student model instance using factory
var record = Factories.StudentFac.CreateOne(fName, lName, dob);
DbContext.Students.Add(record);
DbContext.SaveChanges();
return record;
}
}
And my test looks like this
[TestMethod]
public void StudentManager_AddNewStudent_Test()
{
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3/1/2006");
var student = Managers.StudentManager.AddNewStudent(fName, lName, dob);
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
Upvotes: 0
Views: 454
Reputation: 247068
Your StudentManager
has dependencies buried internally that make it difficult to test. Consider restructuring your design to allow for better testability.
Looking at the StudentManager
the following assumptions were derived...
//An assumed abstraction of the ManagerBase
public abstract class ManagerBase {
public ManagerBase(IDbContext dbContext, IFactory factories) {
DbContext = dbContext;
Factories = factories;
}
public IDbContext DbContext { get; private set; }
public IFactory Factories { get; private set; }
}
//An abstraction of what the unit of work would look like
public interface IDbContext {
//student repository
DbSet<Student> Students { get; }
//...other repositories
int SaveChanges();
}
//Just an example of the Student Factory.
public interface IModelFactory<T> where T : class, new() {
T Create(Action<T> configuration);
}
public interface IFactory {
IModelFactory<Student> StudentFac { get; }
//...other factories. Should try to make your factories Generic
}
With that the target class refactors to...
public class StudentManager : ManagerBase {
public StudentManager(IDbContext dbContext, IFactory factories) : base(dbContext, factories) { }
public Student AddNewStudent(string fName, string lName, DateTime dob) {
// Create a student model instance using factory
var record = Factories.StudentFac.Create(r => {
r.FirstName = fName;
r.LastName = lName;
r.DoB = dob;
});
base.DbContext.Students.Add(record);
base.DbContext.SaveChanges();
return record;
}
}
While it may look like much it will greatly help the testability of your code.
A mocking framework like Moq
can now be used to create a fake version of the database access and factories...
[TestMethod]
public void StudentManager_Should_AddNewStudent() {
//Arrange: setup/initialize the dependencies of the test
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3006-01-03");
//using Moq to create mocks/fake of dependencies
var dbContextMock = new Mock<IDbContext>();
//Extension method used to create a mock of DbSet<T>
var dbSetMock = new List<Student>().AsDbSetMock();
dbContextMock.Setup(x => x.Students).Returns(dbSetMock.Object);
var factoryMock = new Mock<IFactory>();
factoryMock
.Setup(x => x.StudentFac.Create(It.IsAny<Action<Student>>()))
.Returns<Action<Student>>(a => {
var s = new Student();
a(s);
return s;
});
//this is the system/class under test.
//while this is being created manually, you should look into
//using DI/IoC container to manage Dependency Injection
var studentManager = new StudentManager(dbContextMock.Object, factoryMock.Object);
//Act: here we actually test the method
var student = studentManager.AddNewStudent(fName, lName, dob);
//Assert: and check that it executed as expected
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
For production you can create proper implementations of the interfaces and inject them into the classes that depend on them. This answer is based entirely on the example you provided in your post. Take some time to understand the concepts used and also do some more research online. You can then apply these concepts with the remainder of your project as you progress with TDD.
Upvotes: 1