And then take another look...
CB: There we go. Now that the recipes table is empty, let's run our category test again.
Paul: That's more like it. But, and don't take this wrong CB, but that's really not what I'd call a complete test. It seems to me like we need to check a couple more things. First, Rails is telling us it changed "beverages" to "beverage" but we haven't checked to see if there's actually a record in the table named "beverage." And we haven't checked to make sure that there's not a record named "beverages" any more.
CB: I knew you'd be good in the tester role, Paul ;-) You're right. Let's add those tests. So now our test method looks like:
def test_read_and_update rec_retrieved = Category.find_by_name("beverages") assert_not_nil rec_retrieved rec_retrieved.name = "beverage" assert rec_retrieved.save changed_rec = Category.find_by_name("beverage") assert_not_nil changed_rec unwanted_rec = Category.find_by_name("beverages") assert_nil unwanted_rec end
So now when we run category_test.rb, we get
CB: So, we're sure that we can read and update records that are already in the table. Let's make sure we can create and delete records.
Paul: But haven't we already proved that with the fixtures setting up the table and the teardown method clearing it out?
CB: That's a good question, Paul. I think you can argue it both ways.
On one hand, we've definitely shown with the
read_and_update method that the database got loaded, otherwise there'd be nothing to read and update. And we took a peek at the table itself after running the recipe test and saw that the table had been emptied.
On the other hand, there are a couple of things that make me, personally, choose to go ahead and write a couple more methods to test the create and delete capabilities explicitly. First, I look to my test cases to tell me what an app does. If I don't put in methods to test the create and delete functionality, then I have to remember that the app does that but that it's being tested some other way. I guess I'm getting to an age where I'm less apt to trust my memory ;-) Second, my unit tests are testing the Model and I want to see that happen. If you look at the fixture files, you don't see any explicit use of Models per se. So if I've got a lot of trust in my memory, and I trust that Rails itself is working properly, I could argue the additional tests aren't really necessary. But when I've got on my Tester hat, my SOP is "show me," not "trust me."
It's not much work and it's not going to add significantly to our test execution time. So, I'm going to go ahead and add the methods to category_test.rb. Actually, I think I'll combine them like we did for read and update.
def test_create_and_destroy initial_rec_count = Category.count new_rec = Category.new new_rec.save assert_equal(initial_rec_count + 1, Category.count) new_rec.destroy assert_equal(initial_rec_count, Category.count) end
And then we rerun our test case, and...
CB: So now we're sure that, as it stands, our tests make us confident that our Category model is working as designed. Now let's do the same for our Recipe model. Open recipe_test.rb and replace the
test_truth method with:
def test_read_and_update rec_retrieved = Recipe.find_by_title("pizza") assert_not_nil rec_retrieved rec_retrieved.title = "pie" assert rec_retrieved.save changed_rec = Recipe.find_by_title("pie") assert_not_nil changed_rec unwanted_rec = Recipe.find_by_title("pizza") assert_nil unwanted_rec end def test_create_and_destroy initial_rec_count = Recipe.count new_rec = Recipe.new new_rec.category_id = 1 new_rec.save assert_equal(initial_rec_count + 1,Recipe.count) new_rec.destroy assert_equal(initial_rec_count, Recipe.count) end
And now we run the recipe test case...
And it looks like we've got the basics working there too. What do you think, Paul?
Paul: Pretty good so far, CB. Are we ready to start filling those holes we spotted?
CB: You bet. Let's use our tests to put a spotlight on them. The approach I'm hoping you'll help me introduce to Boss says it's best to put off writing application code until you have a failing test that demands it. Let's start with the Category model. In Rails, we use our Unit tests to make sure the Model is working properly. "Properly" means, at a minimum, the CRUD functionality that's at the core of pretty much all Rails apps. That's what we just wrote tests for. The next piece of "properly" means that the validations we need our application to do on the data being written to the database are working. And finally, we need to make sure that any methods we include in our Model are working.
We've decided that all our Category records have to have a name and that the length of the name can't be longer than 100 characters. And we've already seen that we're not currently enforcing that rule. Even if I hadn't given the name field a default value of an empty string, the way it sits right now, a visitor could hit the space bar and effectively do the same thing. So I'd say we're probably going to need to use both validations and a method to check for visitors entering blanks for the name. But let's let the tests tell us what we need.
So let's add another method to our category test case to make sure we only save records that have a valid name. First we'll try to save a record with no name and expect that save to fail, then we'll try to save a record that does have a name and expect it to get saved.
def test_must_have_a_valid_name rec_with_no_name = Category.new assert_equal(false, rec_with_no_name.save) rec_with_name = Category.new(:name => "something new") assert rec_with_name.save end
And now lets run it.