Test Driven Development is great for driving out implementation of features in software development. In this post, I'm going to go through how I write tests using the TDD approach with Rspec by going through the steps to drive out the implementation of simple search feature. We'll implement a method named search_by_title.
How I Write Tests
1) I start by thinking about all the test cases I want to cover. That is, thinking on a high level on what the functionality should achieve. This allows me to have more complete coverage of what I want my code to accomplish instead of diving straight into one test, making it pass and then figuring out what the next test should be.
2) Setup data for tests, perform action and put an assertion on the result of the action.
3) Run test and let it fail.
4) Finally, take small steps to make the test pass. This is because in TDD you want to write the simplest code to make a test pass and also make sure that every piece of code you write is going to be the result of a failing test.
The code
By following step 1, I write out all the test cases I want to cover:
123456789101112131415
require"rails_helper"RSpec.describeVideo,:type=>:modeldoit{shouldbelong_to:category}it{shouldvalidate_presence_of:title}it{shouldvalidate_presence_of:description}describe"search_by_title"doit"returns an empty array if there is no match"it"returns an array of one video for an exact match"it"returns an array of one video for a partial match"it"returns an array of all matches ordered by created_at"it"returns an empty array for a search with an empty string"endend
The video model in app/models/video.rb looks like this:
Next, I'll set up the data I need for the first test, perform an action and make an assertion on the result; step 2. The code for the first test looks like this:
12345
it"returns an empty array if there is no match"dofuturama=Video.create(title:"Futurama",description:"Space Travel!")back_to_future=Video.create(title:"Back to Future",description:"Time Travel")expect(Video.search_by_title("hello")).toeq([])end
Now I'll run the test and let it fail; step 3:
1
rspec spec/models/video_spec.rb
Test is complaining about NoMethodError: undefined method search_by_title for #<Class:0x007f9ec0ad0d98>
Next, make that little change in app/models/video.rb; step 4:
This time the failure is ArgumentError: wrong number of arguments (1 for 0). In our test, we pass a parameter to the method and have to replicate that in the real code.
At this point, if all all method does is to return an empty array this is all we need to satisfy that.
Now when we run the test again, we'll have a passing test:
Fortunately, we have other test cases that we do want to satisfy and by following this simple process for each test, we're forced to drive-out the actually implementation of the method:
Our final code in app/models/video.rb will look like this:
12345678910
classVideo<ActiveRecord::Basebelongs_to:categoryvalidates_presence_of:title,:descriptiondefself.search_by_title(search_term)return[]ifsearch_term.blank?where("title LIKE ?","%#{search_term}%").order("created_at DESC")endend
require"rails_helper"RSpec.describeVideo,:type=>:modeldoit{shouldbelong_to:category}it{shouldvalidate_presence_of:title}it{shouldvalidate_presence_of:description}describe"search_by_title"doit"returns an empty array if there is no match"dofuturama=Video.create(title:"Futurama",description:"Space Travel!")back_to_future=Video.create(title:"Back to Future",description:"Time Travel")expect(Video.search_by_title("hello")).toeq([])endit"returns an array of one video for an exact match"dofuturama=Video.create(title:"Futurama",description:"Space Travel!")back_to_future=Video.create(title:"Back to Future",description:"Time Travel")expect(Video.search_by_title("Futurama")).toeq([futurama])endit"returns an array of one video for a partial match"dofuturama=Video.create(title:"Futurama",description:"Space Travel!")back_to_future=Video.create(title:"Back to Future",description:"Time Travel")expect(Video.search_by_title("urama")).toeq([futurama])endit"returns an array of all matches ordered by created_at"dofuturama=Video.create(title:"Futurama",description:"Space Travel!",created_at:1.day.ago)back_to_future=Video.create(title:"Back to Future",description:"Time Travel")expect(Video.search_by_title("Futur")).toeq([back_to_future,futurama])endit"returns an empty array for a search with an empty string"dofuturama=Video.create(title:"Futurama",description:"Space Travel!",created_at:1.day.ago)back_to_future=Video.create(title:"Back to Future",description:"Time Travel")expect(Video.search_by_title("")).toeq([])endendend