I will use Ruby in this example, and assume that the feature will be built inside rails. Also, this concentrates more on the process and leaves the actual implementation out
One of the reasons why I keep using TDD is that it helps my thinking process when I write code. It makes me think the feature before implenting it making the implementation part really straightforward.
Let’s say I need to create a feature where user can send a message to other user. I’d start with following feature-spec
feature "User can send a message" do scenario "User logs in and sends a message to another user" do user = FactoryGirl.create :user receiver = FactoryGirl.create :user sign_user_in user, "password" click_link "Messages" click_link "Send a new message" fill_in "To", with: receiver.name fill_in "Message", with: "Hello world!" click_button "Send" expect(page).to have_text("Message was sent successfully") expect(receiver.messages.map(&:receiver)).to eq([receiver.name]) end end
This is the first draft of the flow I’d like that user has to go through for sending a message. Most probably it will need changes in the future, but the main point (for me) is try to make sending message as simple as possible. Also note that we’re testing only that user gets A) notification about successful sending and B) there was one message that were sent to correct receiver.
At this point I start to run specs and write production code until I need to create a new class (e.g. controller).
Next spec file will be for
MessagesController (as we don’t have controller named like that yet).
RSpec.describe MessagesController, type: :controller do describe "#create" do context "receiver exists" do it "message is sent with username" it "message is sent with userid" it "message is sent with email" end context "receiver does not exist" do it "message is not sent" end end end
So before I write any line of tests, I try to write down the “features” of the class I’m testing. As this is a new feature and we’re missing the
Message object, we’ll need to hop on to write spec file for that
Note: In some cases, there’s no point for creating specs for controller (for example if action is just listing items, that can be tested in feature spec just fine). But in this example, we’re not sure how we’re going to implement the feature, so I’d follow the style and create the spec file (we can always refactor specs and remove them if they become obsolete).
On the model, I follow same process as with controller (and other classes), drafting out what the class should do.
RSpec.describe Message, type: :model do describe "#send_message" do context "receiver exists" do it "message is sent with username" it "message is sent with userid" it "message is sent with email" end context "receiver does not exist" do it "message is not sent" end end end
As we can see, it’s pretty similar with our controller spec file. So after we’ve implemented
Message, we can refactor the controller.
Refactor controller spec
So we have now tests to test that message can be sent with username, userid and email, we don’t need to test the same things in controller. Instead, we should just test that message is either sent or not.
RSpec.describe MessagesController, type: :controller do describe "#create" do context "receiver exist" do it "message is sent" end context "receiver does not exist" do it "message is not sent" end end end
Now we’re testing two paths, happy and unhappy. And to be honest, we probably should get rid off the whole controller spec by adding unhappy-feature spec. But it’s ultimately up to you and how you like to test things.
As feature specs are “expensive”, so we shouldn’t test everything there. But testing one happy and one unhappy path from end to end may serve the purpose.
This is how I design my code with TDD. I’ve found it really helpful to keep focus on the thing I’m working on. I either think and design the functionality or I follow my decisions and write the code “without-thinking”.