How to do before(:all)/after(:all) in minitest
Are you looking for a case not here?
So you like to write your tests in spec format (using describe
,
before
, after
, and it
) and running them with
minitest. You have a set of
tests with a common setup and that setup is orders of magnitude more
expensive than the body of each test, for example.
require 'minitest/autorun'
describe 'end-to-end test on main component of RSS reader' do
# before executes before each test
before do
# HTTPServer takes a couple of seconds to start
@http_server = HTTPServer.new 'localhost', 9090
end
# after executes after each test
after do
@http_server.stop
@http_server.join
end
it 'main component gets RSS feed' do
# Takes several milliseconds to complete
main.get('http://localhost:9090/feed.xml').wont_be nil
end
it 'main component raises RuntimeError when given malformed RSS feed' do
# Takes several milliseconds to complete
->{ main.get('http://localhost:9090/bad.xml') }.must_raise RuntimeError
end
end
Running the example takes you 4 to 5 seconds because starting the HTTP
server is expensive. You would rather setup once before all tests and
teardown once after all tests. In the case where you are a practical
person, you use blocks before(:all)
and after(:all)
provided by gem
minitest-hooks like so.
require 'minitest/autorun'
require 'minitest/hooks/default'
describe 'end-to-end test on main component of RSS reader' do
# before(:all) executes before any test is run
before(:all) do
# HTTPServer takes a couple of seconds to start
@http_server = HTTPServer.new 'localhost', 9090
end
# after(:all) executes after all tests have been run
after(:all) do
@http_server.stop
@http_server.join
end
it 'main component gets RSS feed' do
main.get('http://localhost:9090/feed.xml').wont_be nil
end
it 'main component raises RuntimeError when given malformed RSS feed' do
->{ main.get('http://localhost:9090/bad.xml') }.must_raise RuntimeError
end
end
In the case where you prefer to do without minitest-hooks, you use
a class instance
variable
and a minitest after_run
block
like so.
require 'minitest/autorun'
describe 'end-to-end test on main component of RSS reader' do
# The block assigned to @expensive executes before any test is run
@http_server = begin
# HTTPServer takes a couple of seconds to start
HTTPServer.new 'localhost', 9090
end
# The block given to Minitest::after_run executes way after all
# tests have been run, right before minitest stops.
Minitest.after_run do
@http_server.stop
@http_server.join
end
it 'main component gets RSS feed' do
main.get('http://localhost:9090/feed.xml').wont_be nil
end
it 'main component raises RuntimeError when given malformed RSS feed' do
->{ main.get('http://localhost:9090/bad.xml') }.must_raise RuntimeError
end
end
I have nested describe blocks
Watch out. minitest-hooks executes a given before(:all)
before EACH
nested describe block and a corresponding after(:all)
after EACH
describe block. Consider the following spec file.
require 'minitest/autorun'
require 'minitest/hooks/default'
describe "spec file, eager evaluation, nested describe block, minitest-hooks" do
before(:all) do
puts 'executing something expensive'
@expensive = 'woah! expensive!'
end
after(:all) do
puts "cleaning expensive value: #{@expensive}"
end
describe 'describe1' do
it 'test1' do
puts "test1: #{@expensive}"
end
it 'test2' do
puts "test2: #{@expensive}"
end
end
describe 'describe2' do
it 'test3' do
puts "test3: #{@expensive}"
end
it 'test4' do
puts "test4: #{@expensive}"
end
end
end
The spec file will give you an output like the following.
ruslan$ bundle exec rake
Run options: --seed 36717
# Running:
executing something expensive
test1: woah! expensive!
.test2: woah! expensive!
.cleaning expensive value: woah! expensive!
executing something expensive
test4: woah! expensive!
.test3: woah! expensive!
.cleaning expensive value: woah! expensive!
Finished in 0.002084s, 1919.5342 runs/s, 0.0000 assertions/s.
4 runs, 0 assertions, 0 failures, 0 errors, 0 skips
To execute your setup before all describe blocks and your teardown after all describe blocks, you could do the following.
require 'minitest/autorun'
describe "spec file, eager evaluation, nested describe block" do
$expensive = begin
puts 'executing something expensive'
'woah! expensive!'
end
def self.expensive
$expensive
end
Minitest.after_run do
puts "cleaning expensive value: #{$expensive}"
puts "cleaning expensive value: #{expensive}"
puts "cleaning expensive value: #{self.expensive}"
end
describe 'describe1' do
it 'test1' do
puts "test1: #{self.class.expensive}"
end
it 'test2' do
puts "test2: #{self.class.expensive}"
end
end
describe 'describe2' do
it 'test3' do
puts "test3: #{self.class.expensive}"
end
it 'test4' do
puts "test4: #{self.class.expensive}"
end
end
end
If you have aversion to global variables, you can do the following.
require 'minitest/autorun'
class ClassEagerNested < Minitest::Spec
@@expensive = begin
puts 'executing something expensive'
'woah! expensive!'
end
Minitest.after_run do
puts "cleaning expensive value: #{@@expensive}"
end
describe 'describe1' do
it 'test1' do
puts "test1: #{@@expensive}"
end
it 'test2' do
puts "test2: #{@@expensive}"
end
end
describe 'describe2' do
it 'test3' do
puts "test3: #{@@expensive}"
end
it 'test4' do
puts "test4: #{@@expensive}"
end
end
end
I am looking for another case
You know what, maybe you like writing specs or classes, maybe you have a flat or nested describe block, maybe you want eager or lazy evaluation, maybe you like using minitest-hooks or not. Why don’t you have a look at 16 cases?
FYI, issue 61 of
minitest inspired me
to write this post and the 16 cases. You may want to have a look at
that for an idea of why minitest does not come with before(:all)
/after(:all)
.