Testing attributes with Capybara

Bit of a nitpick but I find myself testing attributes in my feature specs and am beginning to wonder if this is good practice?

For example, I may have a form object which updates multiple models and so I end up with abstracted methods in my capybara spec that look like:

def user_fills_in_form(form_attributes)
  # fill_in ..., with: ...

def model_attributes_should_equal(args)
  # expect(something.attribute).to eq args.fetch(:attribute_name)

def another_model_attributes_should_equal(args)
  # expect(something.attribute).to eq args.fetch(:attribute_name)

Perhaps I’m doing outside-in TDD wrong - as I think I end up with this situation when I’m trying to test that everything is working correctly from my feature spec before dropping down into the form object unit test.

Is this really an issue - are you strict about this? Is this approach acceptable? Or should you strictly test attribute assignment in your unit tests?

1 Like

If I’m testing the attributes of a model, that’s where I do it.

I prefer my integration tests to focus on what I can see in the UI. I rarely reach into the database from them.

If you want to confirm that submitting ‘John Smith’ in a form will later display “Welcome John Smith!”, that’s fine. But I wouldn’t check the DB to see if the name was set to ‘John Smith’.

1 Like

That’s what I do as well. Checking the database would be an implementation detail. Using something like expect(User.all.last.name).to eq 'bob' in feature tests is especially bad. You can’t switch databases or ORMs without breaking the test. So if you need to check for attributes I’d abstract it like you did, @ralphwintle.

I have a similar issue with feature tests when testing GET requests though: It seems to be common practice to create some database records (using factory_girl for example) first and then assert against some elements on the page. Isn’t setting up the database records “reaching into the space capsule”[1] and therefore an implementation detail? We wouldn’t do this kind of stuff when testing an array implementation[2]:

array = MyArray.new
array.add 3
expect(array.include?(3)).to eq true
expect(array.size).to eq 1

So, should we test our GET`` requests in a similar way? Should we test GETat all or just use it to verifyPOST`'s correct behavior? Here’s an example [3]:

before do
  post '/api/v1/posts', {title: 'Testing attributes with Capybara'}

it 'returns all created posts' do
  get '/api/v1/posts'
  expect(json_response['posts'].size).to eq 1
  expect(json_response['posts'][0]['title']).to eq 'Testing attributes with Capybara'

This has the nice advantage of being pretty robust against any changes concerning the persistence layer. On the other hand this test will fail if either of the two implementations (GET or POST) don’t work. Not much of an issue in my opinion.

I would love to her some opinions on that one.

[1] Rails Conf 2013 The Magic Tricks of Testing by Sandi Metz - YouTube.
[2] Well, to be fair: There’s no easy way to reach into the MyArray object without relying on hacks.
[3] Using API specs instead of a webpage for simplicity’s sake.

@benorenstein good to know.

So I think what actually happened was I was creating a pretty standard resource using outside-in development and got a passing spec (testing successful scenario only focused on the UI) without specifying any attributes in my strong parameters e.g.

def invoice_params
    # spec passed with this not filled out

To make sure I was getting the correct attributes set, I then wrote value expectations on my attributes in my feature spec.

Now that I think about it, the better approach would have been to create a bunch of specs around the validations (using Shoulda Matchers) and then create a factory with the minimum requirement to pass those validations so you can expect the object to be_valid. That way the form won’t result in the successful path unless the resource has the necessary attributes.

Attributes without validations would just get caught/tested in other feature specs where those attributes may appear in the UI.

I’m guessing this is similar to how’d you do it?

Interestingly, asked a few people at work and got mixed answers (some did test attributes in feature specs and some didn’t).

Interesting. So it seems like you’re asking if it’s OK to skip part of the setup phase in your integration test by using tools such as FactoryGirl or whether to implement all the steps required (i.e. exercising the post in a before block) so that you may test the expected outcome?

I don’t see too much issue with the way you’ve done it, although following the TB style guide of including the 4 phase test pattern (getting rid of the before block) and using a Factory may make it a bit more readable.

I’m not quite sure what I had in mind made it across the internet.

So, to put it simply: Don’t use factory_girl, ActiveRecord or any other stuff which interacts with the database in your integration [1] tests. It makes them brittle. If the user doesn’t see a change[2] to your application your integration tests shouldn’t need to be changed.

(That’s the extreme opinion. There might be exceptions where setup is actually required (e.g. your app might require some kind of Country data))

To come back to my MyArray analogy: You wouldn’t set some instance variables within the class in the setup phase of your test, because this would obviously break if you change the implementation from using a tree to using a linked list.

[1] I mean the stuff which goes into the features folder in RSpec. Integration tests actually cover a much larger range of tests and are not restricted to UI interactions.

I don’t know. It seems to me that setup may depend on a case by case basis i.e. whether you go for end to end testing including each step in your setup phase or having an integration spec that tests ‘smaller’ groups of behavior, if that makes sense?

I guess if you were asking for opinions, I wouldn’t be against using factory girl as part of my setup in a feature spec, providing it was called for… I wouldn’t call it an extreme opinion either.

Perhaps not such a good example, but in the Clearance gem they have some rack middleware that lets you

visit root_path(as: user)

This is sort of hinting at what I’m suggesting in that in some circumstances I think it’s OK to bypass some setup to get to where you want in order to test your UI.

Just my 2 cents. Interested to hear other peoples thoughts.

I can’t say I agree with this.

I regularly perform setup for integration specs using factory_girl and have been quite happy with the results.

I do agree it increases brittleness, but only slightly, and comes with a large win in terms of speed (you don’t have to navigate 5 page loads to get the system set up) and test clarity (it’s sometimes much more obvious what you’re testing when you can set it up without clicking on a bunch of UI elements).

1 Like