4

For a messaging app we have some fairly long integration tests. Right now, to run through a given scenario something like 20 or 30 steps have to be done in a certain sequence because Step N replies on data created during step N-1.

So they are currently organized under a single 'it ... do' example:

describe WidgetController do
  describe "long opt-in opt-out scenario" do
    it "can complete the full sequence" do
      # create an account factory with trait account_type
      # create manager factory for the account
      # 5 people factories opt-in to that account
      # 1 person then opts out
      # a manager creates and sends a broadcast
      # the one opt-out does not get the message
      # the four opt-ins get the message
      # verify the format of the message

Each step has one or two assertions to verify that step did the right thing. So currently we have 40+ assertions in a single it...do example which completely obscures what's actually being tested.

We could insert a puts statement at each step to push a description to the output, but it would certainly be better if our N steps could be treated like N rspec examples.

Is there any way to organize N sequential integration test steps as N separate it...do examples so (a) they are sequentially run, and (b) state is maintained between steps, and (c) (ideally) allow us to continue using the random seeded order of other tests we run?

jpw
  • 18,697
  • 25
  • 111
  • 187

2 Answers2

0

I'd separate the testing from preparing the test to make it cleaner. This means in your example for this one long test:

  # create an account factory with trait account_type
  # create manager factory for the account
  # 5 people factories opt-in to that account
  # 1 person then opts out
  # a manager creates and sends a broadcast
  --> no asserts until now, because that is test preparation

You could put this into before(:all) and then make separate tests:

 before(:all) do
     # no asserts here, just the steps to do it
     # create an account factory with trait account_type
     # create manager factory for the account
     # 5 people factories opt-in to that account
     # 1 person then opts out
     # a manager creates and sends a broadcast

     # so in the end you got:
     @people = ...
     @manager = ...
     @opt-ins = ...
     @opt-out = ...
     @broadcast = ...
 end

 it "should not send the message to the opt-out" do
     # the one opt-out does not get the message
 end

 it "should send the message to the four opt-ins" do
     # the four opt-ins get the message
 end

 it "should have the right message format" do
     # verify the format of the message
 end

In addition, you should also test the steps of the before(:all) in separate tests, too:

it "should be able to create an account with account_type" do
     # create an account factory with trait account_type
     # assert it worked
end

it "should be able to create a manager for an account" do
     # create an account factory with trait account_type
     # no assertion that it worked (that is tested before)

     # create manager factory for the account
     # assert manager got correctly created
end

it "should be able to opt-in to accounts" do
     # create an account factory with trait account_type
     # no assertion that it worked (that is tested before)

     # 5 people factories opt-in to that account
     # assert that it worked
end

it "should be able to opt-in to accounts" do
     # create an account factory with trait account_type
     # 5 people factories opt-in to that account

     # 1 person then opts out
     # assert that it worked
end

There is a small code duplication, but that makes the tests simple and clearer to read and that's why I'd go for that.

Finally, to organize your tests, use a shared_context. So if you need to prepare the same things in different tests/files, include them as a shared_context:

# spec/contexts/big_message_broadcast.rb
shared_context "big message broadcast" do
    before(:all) do
         # no asserts here, just the steps to do it
         # create an account factory with trait account_type
         # create manager factory for the account
         # 5 people factories opt-in to that account
         # 1 person then opts out
         # a manager creates and sends a broadcast

         # so in the end you got:
         @people = ...
         @manager = ...
         @opt-ins = ...
         @opt-out = ...
         @broadcast = ...
     end
end

# spec/.../some_spec.rb
describe "long opt-in opt-out scenario" do
     include_context 'big message broadcast'
     it "should not send the message to the opt-out" do
         ...
     end
     ...
end

That way you can simply use include_context 'big message broadcast' to have it prepared wherever you like.

Markus
  • 5,667
  • 4
  • 48
  • 64
0

You might want to try using Capybara Test Helpers, a library to encapsulate and modularize integration tests.

By encapsulating different steps into separate test helper methods and giving them descriptive names, the test should become easier to read.

Another advantage, is that once you have split the flow into different methods, you can get incremental output by simply enabling it.

output

Maximo Mussini
  • 1,030
  • 11
  • 19