This is a companion discussion topic for the original entry at https://thoughtbot.com/upcase/videos/getting-the-tdd-ball-rolling
@benorenstein, if you wanted to drive out validations for a book, i.e. a book must have a title. Would you write the feature spec then move down into unit testing?
This seems fine in the case of a simple title-check, but it doesnāt feel scalable for large forms where you are testing every permutation of error states for the model in both the feature specs and the unit specs.
Nope, Iād just write a unit test for that directly.
We used to test such things with integration tests, but we found the payoff didnāt really offset the costs of the integration tests (slow, less direct to write).
@benorenstein do you do the same thing of writing minimal code to make the code pass while working on real projects? I mean will you wait for the rspec to show errors before you fill in the index action or do you get ahead of the time and write down the index action knowing what it should be doing before writing the test for it? Doesnāt this slow you down?
@benorenstein I was wondering about āSees a count of the total books in libraryā scenario - wouldnāt it be better to replace the steps for adding a book with:
Book.create # or even use factory_girl or something else to not worry about creating valid models
visit root_path # to reload the page
Testing these 3 steps seems kind of repetitive (and somewhat prone to errors ;)) to me because youāre exercising the same behaviour as in the previous scenario. Whatās your take on this?
The more I think about my question the more I feel that feature specs shouldnāt know about Book
model at all. Iām also watching Intermediate Ruby on Rails part 3 right now where @halogenandtoast does a refactoring and talks how the core appearance of the application hasnāt changed while he brakes the code into smaller pieces so it makes sense not to test lower level details like Book.create
in feature specs and instead let these āoutsideā specs make sure we havenāt broken functionality while refactoring. Am I on the right track here?
Yep, absolutely. I work in very small steps.
A test that you havenāt seen fail is not a good test. I try to only write the code that the test error is telling me to write. This helps ensure coverage, and helps me get in a good rhythm.
Every once in a while Iāll jump ahead just a bit. For example, if I write a test that says the foo
method should return bar
, Iāll run the test, watch it fail, and then implement the method, rather than simply defining an empty method foo
. But I actually enjoy programming more when Iām being very disciplined about only writing the minimal thing to make the testās error message change (or pass).
Thanks Ben
Hi @benorenstein and thanks for the great video. As always with thoughtbot, itās a awsome resource.
I have a few questions. If I understand, you donāt respect the Sandi Metzās rules in this example because you instantiate two objects in the controller. Think than the thoughtbotās philosophy is to not use a more complex pattern if there is no problem. Am I right?
@benorenstein, in one of your talk on vim you said than you use vimās tabs only for completely unrelated things. In this video, do you use tabs just for demo?
This video is definitively the kind of videos Iām looking for. I will be very happy if you continue to develop this kind of application with more complex concept. For example, how and when do you use services or form objects.
Thatās correct. We follow Sandiās rules sometimes, but when it doesnāt seem to make sense to us, we ignore them.
Since the focus of this screencast was early TDD, I didnāt want to cover presenter objects (which is what I might pass in if I only wanted to pass one thing to my view). However, I might refactor to that later if I felt like we were passing too much to the view, or if I thought there were a missing object that did a good job of encapsulating everything I was passing.
Did I say that? I donāt recall that. I mix and match my tabs and windows all the time. When Iām working, I constantly open new tabs and splits and move them around and close them.
Glad to hear it!
Did I say that? I donāt recall that. I mix and match my tabs and windows all the time. When Iām working, I constantly open new tabs and splits and move them around and close them.
http://www.youtube.com/watch?v=SkdrYWhh-8s. Near 29 minute 10 second. Again, a great talk.
Do you plan to do a serie (build a rails app from scratch) or is it a single video?
I didnāt plan on doing a series. Joshās TDD workshop does build up an app over a longer period, so I think that is actually covered fairly well by existing content. What do you think?
@benorenstein Are you coming up with more prime challenges? Maybe this time you could give a rails app as a problem :).
Yes! Soon!
For sake of example, letās say we had a very complicated dashboard, displaying a lot of information ( I know this is probably not good interface design ). Would you have an integration test for each one of those parts, or would you instead unit test the dashboard presenter, or both?
Good question!
I think I would write just one integration test for the happy path: visit the dashboard and verify that the important information is present.
Iād drive out the actual methods on the presenter with unit tests.
This way, you get high-level coverage of the happy path (easy), and the edge-cases can be covered by lower-level unit tests (easier than integration testing them).
@benorenstein makes perfect sense.
One more question: Iāve been using page objects in my integration tests and Iām really enjoying them over using āexpect(page)ā, but I ran into something last night that I was a little fuzzy about.
Letās say we have a āShoutOnPageā for the shouts index action. You can click on a shout and that would take you to the shouts show action.
scenario 'visit shout page' do
user = create(:user)
shout = ShoutOnPage.create_shout_for(user)
visit shouts_path(as: user)
shout.click
expect(shout).to be_header_of_page
end
First, is it bad practice to make page objects clickable like āshout.clickā? Furthermore, is it bad practice to be transferring the page object to the show view and asserting something about it there (i.e. in a āheader_of_page?ā method)?
Not sure, as I havenāt used Page Objects much. @joshclayton, any thoughts?
@dolphorama to answer your first question, I donāt think itās bad practice to make them clickable - youāll just want to be sure each page object is discrete and obvious in how it can be interacted with. A good example there would be a master/detail representation where the object in both the master and detail (when the detail is visible) is clickable.
To answer your second question, the fact that youāre bringing this up is great - it exposes what I see to be a breakdown of the page objects and something Iād do differently. Iām definitely not opposed to methods like header_of_page?
, but it (at least to me) isnāt obvious what thatās testing. I avoid asserting against markup as the name of the method (Iām guessing itās testing that the name is in a header tag) and instead focus on the behavior of the object itself. In this case, because itās a very simple assertion, Iād just check presence (where present?
would verify basic information is there on the show page, like title and description).
The other thing Iād do is have two separate page objects - one for the ShoutsOnPage
(the index page itself) and a ShoutOnPage
. In that situation, youād be able to do something like:
shouts = ShoutsOnPage.new
shout = shouts.create_for(user)
shouts.view # not necessarily necessary, since the create controller action will likely take the user to the index
shouts.first.click
expect(shout).to be_present
This benefits from a single level of abstraction - the page object handles ALL page interaction so thereās no mixture of Capybara methods (like visit
) and page object interaction.
I think the best way to learn about page objects is try a handful of different patterns and see what works and what doesnāt work. I still feel pretty new to the concept of them and how best to handle these situations so Iāve been trying different things in different projects. The biggest takeaway I can provide is to follow the single level of abstraction - youāll want your page objects to encapsulate all page interaction, so there shouldnāt really be any Capybara methods directly written in the scenarios.
Feel free to at-reply me if anything else comes up or if you have any questions, and good luck - page objects are a lot of fun and make writing tests much easier in the long run!