I am fairly new to Test Driven Development and I just started learning the SOLID principles so I was hoping someone could help me out. I'm having some conceptual trouble understanding the Single Responsibility Principle in the context of developing unit tests and methods in the TDD paradigm. For instance, say I want to develop a method that deletes an item from a database- before my code would have looked like the following...
I'd start with defining a test case:
"Delete_Item_ReturnsTrue": function() {
//Setup
var ItemToDelete = "NameOfSomeItem";
//Action
var BooleanResult = Delete( ItemToDelete );
//Assert
if ( BooleanResult === true ) {
return true;
} else {
console.log("Test: Delete_Item_ReturnsTrue() - Failed.");
return false;
}
}
I'd run the test to make sure it failed, then I'd develop the method...
function Delete( ItemToDelete ) {
var Database = ConnectToDatabase();
var Query = BuildQuery( ItemToDelete );
var QueryResult = Database.Query( Query );
if ( QueryResult.error !== true ) {
//if there's no error then...
return true;
} else {
return false;
}
}
If I'm understanding the Single Responsibility Principle correctly, the method that I originally wrote had the responsibilities of deleting the item AND returning true if there wasn't an error. So if I follow the SOLID paradigm the original method should be refactored to look something like...
function Delete( WhatToDelete, WhereToDeleteItFrom ) {
WhereToDeleteItFrom.delete( WhatToDelete );
}
Doing the design as such, changed my method from a boolean function to a void function so now I can't really test the new method in the same manner I was testing my old method.
I guess I could test for thrown exceptions but then doesn't that make it an integration test rather than a unit test because there's no actual exception thrown in the method?
Do I design and implement an extra function which checks the database for use in my unit test?
Do I just not test it because it's void? How exactly does that work in TDD?
Do I pass in a mock repository and then return it after it's state has changed? Doesn't that just essientially bring me back to square one?
Do I pass in a reference to a mock repository and then just test against the repository after the method completes? Wouldn't that be considered a side effect though?
So the single responsibility principle says: They should be exact one reason when I need to change a function.
So let's look at your function:
function Delete( ItemToDelete ) {
var Database = ConnectToDatabase();
var Query = BuildQuery( ItemToDelete );
var QueryResult = Database.Query( Query );
if ( QueryResult.error !== true ) {
//if there's no error then...
return true;
} else {
return false;
}
}
So on the first look, your function looks good. The naming on some places is a little bit confusing. A function should be start with a small letter. For example "connectToDatabase()". It is a little bit surprising that connectToDatabase returns an Object.
The name BuildQuery seems to be wrong, because BuildQuery( myItem ) returns a query wich is deleting something.
But I would never have such a long complicated function which just one testcase.
You need to write more test cases. For the first test case you could write the function like that:
function Delete( ItemToDelete) {
return true
}
The next testcase could be "call the buildDeleteQuery function with the item id". At that point you need to think about how you call your db. If you have a dbObject, which does that you could mock that Object.
function Delete( ItemToDelete ) {
buildDeleteQuery( ItemToDelete.id )
return true
}
Now more and more test cases. Remember that there will be also test cases for buildDeleteQuery. But for the outer function you could mock buildDeleteQuery.
Now to answer some of your questions:
But the most important thing: Keep practicing! When you start with TDD it takes some time. A good and fun resource is: Uncle Bobs Bowling Kata Just download the slides from the website and look how tdd is done step by step.