Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rspec let() cleanup

Tags:

ruby

rspec

Is there a way to keep track of variables that are created when using let?

I have a series of tests, some of which use let(:server) { #blah blah }. Part of the blah is to wait for the server to start up so that it is in a decent state before it is used.

The issue comes when I'm done with that test. I want to kill the server using server.kill(). This would be almost perfect if I could say something to the effect of

after(:each) { server.kill }

But this would create the server and waste all the resources/time to create it when it is referenced, only to kill it immediately if the server hadn't been used in the preceding test. Is there a way to keep track of and only clean up the server if it has been used?

like image 717
rynojvr Avatar asked Jun 06 '13 20:06

rynojvr


3 Answers

I've come across a similar problem. A simple way to solve this is to set a instance variable in the let method to track if the object was created:

describe MyTest do

  before(:each) { @created_server = false }

  let(:server) { 
    @created_server = true
    Server.new 
  }

  after(:each) { server.kill if @created_server }

end
like image 65
David Miani Avatar answered Oct 18 '22 10:10

David Miani


What I would do is something like this:

describe MyTest do
  let(:server) { Server.new }

  context "without server" do
    ## dont kill the server in here.
  end

  context "with server" do

   before do
     server
   end

   after(:each) { server.kill }

   it {}
   it {}
  end
end
like image 41
Arthur Neves Avatar answered Oct 18 '22 10:10

Arthur Neves


This is definitely a hack:

describe "cleanup for let" do
  let(:expensive_object) {
    ExpensiveObject.new
  }
  after(:context) {
    v = __memoized[:expensive_object]
    v.close if v
  }
end

I figured that rspec had to be storing these lazy values somewhere the instance could access them, and __memoized is that place.

With a helper, it becomes a bit tidier:

def cleanup(name, &block)
  after(:context) do
    v = __memoized[name]
    instance_exec(v, &block) if v
  end
end

describe "cleanup for let" do
  let(:expensive_object) {
    ExpensiveObject.new
  }
  cleanup(:expensive_object) { |v|
    v.close
  }
end

There's still room for improvement, though. I think I would rather not have to type the object's name twice, so something like this would be nicer:

describe "cleanup for let" do
  let(:expensive_object) {
    ExpensiveObject.new
  }.cleanup { |v|
    v.close
  }
end

I'm not sure I can do that without hacking rspec to pieces, but maybe if rspec themselves saw the benefit of it, something could be done in core...

Edit: Changed to using instance_exec because rspec started whining if things were called from the wrong context, and changed cleanup to be after(:context), because apparently this is the level it's memoising at.

like image 42
Hakanai Avatar answered Oct 18 '22 09:10

Hakanai