-->

How can I test multistep forms with Capybara & Min

2019-08-14 15:44发布

问题:

I have a single page Sinatra app that has a multistep form/wizard interface. If I want to test the form with Capybara, will need to repeat all the steps for each test? I'm hoping to avoid something like this:

it "visits the home page" do 
  vist "/"
  page.should have_content('foo')
end

it "clicks the first button" do 
  vist "/"
  page.should have_content('foo')
  button_click("Next")
end

it "clicks the second button" do 
  vist "/"
  page.should have_content('foo')
  button_click("Next")
  page.should_have_content('bar')
end

it "clicks the third button" do 
  vist "/"
  page.should have_content('foo')
  button_click("Next")
  page.should_have_content('bar')
  button_click("Next")
end

I found an article about nested tests using RSpec and Capybara, but haven't been able to get a similar technique to work with Minitest.

回答1:

I've done some research for you, and I'll share with you what I've found.

In order for doing this, you should consider "converting" your Minitest tests, to specs. This gives you access for similar syntax, as RSpec's - describe with capability of nesting them.

I'll simplify the code to provide example, but it should be clear where to put your logic.

Let's check couple examples.

1). Let's do some simple test for Array:

require "minitest/autorun"

describe Array do
  before do
    @arr = []
  end

  describe "when initialized" do
    it "is empty" do
      @arr.length.must_equal 0
    end
  end
end

This should pass, nothing really fancy here.

2). Let's add another describe, nested to one we have:

describe Array do
  before do
    @arr = []
  end

  describe "when initialized" do
    it "is empty" do
      @arr.length.must_equal 0
    end

    describe "when element added" do
      it "length reflects the change" do
        @arr << "a"
        @arr.length.must_equal 1
      end
    end
  end
end

This also works - an element has been added to the array, and its length indicates that properly.

3). Let's try nesting another block. We're hoping, that @arr << "a" will be preserved, so if we add another element, @arr.length will be 2. Let's see:

describe Array do
  before do
    @arr = []
  end

  describe "when initialized" do
    it "is empty" do
      @arr.length.must_equal 0
    end

    describe "when element added" do
      it "length reflects the change" do
        @arr << "a"
        @arr.length.must_equal 1
      end

      describe "when another element added" do
        it "length also reflects the change" do
          @arr << "b"
          p @arr
          @arr.length.must_equal 2
        end
      end
    end
  end
end

describe is nested within another describe - as we would do for RSpec, but unfortunately, it seems that the result of @arr << "a" is not preserved, and the nested describe's @arr.length is also 1.

I have left p @arr in the code, so you can easily see in your console what is currently stored in @arr.

Definitely not what we expected... Let's try something crazy then...

4). Let's nest describe within... it:

describe Array do
  before do
    @arr = []
  end

  describe "when initialized" do
    it "is empty" do
      @arr.length.must_equal 0
    end

    describe "when element added" do
      it "length reflects the change" do
        @arr << "a"
        @arr.length.must_equal 1

        describe "when another element added" do
          it "length also reflects the change" do
            @arr << "b"
            p @arr
            @arr.length.must_equal 2
          end
        end
      end
    end
  end
end

Well, it turns out, that this is completely valid and it behaves exactly as we expected! (Again, p @arr left here so you an check in console what is currently stored in @arr).

To be honest - I didn't check that with Capybara, but this simple example is quite promising. Try modifying your code accordingly, so in your specs the Capybara interactions are implemented.

Let me be clear about provided solution: I strongly advise agains this approach:

  • Specs like this are difficult to read - and in result - difficult to maintain.
  • This is considered bad pattern. Next steps should not rely on result of previous steps.

If you want to test your contoller properly, you should try something similar to this (this is pseudo code just to illustrate idea):

Test for step 1

post "/url-to-my-form", { params_step_1: { ... } }

Test for step 2

post "/url-to-my-form", { params_step_1: { ... }, params_step_2: { ... } }

With this approach is very easy to see what params are posted, thus it's easier to test against eg. violations of any rules (empty values, invalid email, etc...).

Hope that helps! Good Luck!