I'm trying to write a test for ActiveRecord - and Rails uses MiniTest for its tests, so I don't have a choice of test framework. The condition I want to test is this (from the db:create rake tasks, pulled into a method for the purpose of this example):
def create_db
if File.exist?(config['database'])
$stderr.puts "#{config['database']} already exists"
end
end
So, I want to test that $stderr receives puts if the File exists, but otherwise does not. In RSpec, I would have done this:
File.stub :exist? => true
$stderr.should_receive(:puts).with("my-db already exists")
create_db
What's the equivalent in MiniTest? assert_send doesn't seem to behave as I expect (and there's not really any documentation out there - should it go before the execution, like should_receive, or after?). I was thinking I could temporarily set $stderr with a mock for the duration of the test, but $stderr only accepts objects that respond to write. You can't stub methods on mocks, and I don't want to set an expectation of the write method on my stderr mock - that'd mean I'm testing an object I'm mocking.
I feel like I'm not using MiniTest the right way here, so some guidance would be appreciated.
An update: here is a solution that works, but it is setting the expectation for :write, which is Not Right.
def test_db_create_when_file_exists
error_io = MiniTest::Mock.new
error_io.expect(:write, true)
error_io.expect(:puts, nil, ["#{#database} already exists"])
File.stubs(:exist?).returns(true)
original_error_io, $stderr = $stderr, error_io
ActiveRecord::Tasks::DatabaseTasks.create #configuration
ensure
$stderr = original_error_io unless original_error_io.nil?
end
So, it turns out Rails uses Mocha in combination with Minitest, which means we can take advantage of Mocha's far nicer message expectations. A working test looks like this:
def test_db_create_when_file_exists
File.stubs(:exist?).returns(true)
$stderr.expects(:puts).with("#{#database} already exists")
ActiveRecord::Tasks::DatabaseTasks.create #configuration
end
Related
We have a RoR application, Rspec for tests with Webmock for HTTP requests.
After having to do some refactoring in our legacy codebase, I realized that many of our tests had unnecessary stubs.
Like this example, the do_a function has been refactored so that we don't do any api call so the stub_request is not necessary anymore, worse, it should be removed.
it 'does something' do
stub_request(:get, 'http://something.com/users/123')
do_a
expect(..)
end
One way of fixing this is:
it 'does something' do
stub_something = stub_request(:get, 'http://something.com/users/123')
do_a
expect(..)
expect(stub_something).to have_been_requested.once
end
But I'd like to enforce this directly through a strict mode where the test fails if any declared stub has not been called ? The first example would then fail automatically.
Thanks a lot for your help
You want to use expectations instead of stub_request:
expect(WebMock).to have_requested(:get, "http://something.com/users/123").once
# or
expect(a_request(:get, "http://something.com/users/123")).to have_been_made.once
But I'd like to enforce this directly through a strict mode where the test fails if any declared stub has not been called?
I don't think this is really possible unless you do some heavy monkeypatching - and it seems like a bad idea instead of just refactoring your tests.
I have been looking around the internet for a long, frustrating, while, and I'm still quite confused as to what the purpose of the teardown() method is in MiniTest and how it should be used.
The basic gist I have is that it is 1-run after each test, and 2-undoes things that were done during the test in order to allow future tests to run in a clean environment.
However, I am unclear on the nature of things it needs to undo: Does it need to empty the DB? Reset class variables? etc.
I am also unclear on whether the method is supposed to be explicitly filled out or not. I have found many examples where teardown() is completely left out of the example.
(My best guess is that there is a super-method teardown that runs automatically and takes care of certain things. That would explain why it is often left out, and would also explain why some things are reset in a given teardown() method and some aren't. But I still don't know which things are and which aren't.)
In short:
Does teardown need to be explicitly created? In what circumstances would it need to be overwritten and in which wouldn't it be?
The simplest answer is that you use #teardown in every test but you don't need to worry about it. Similar to the Rails lifecycle, there is a Minitest lifecycle. There are hooks to inject logic and behavior to be used by your tests. The main one in Rails tests is the database transaction. Each test that uses ActiveSupport::TestCase runs in a database transaction. The flow is like this:
Create the database transaction (Minitest::Test#setup)
Run your test method (MyTest#test_something)
Rollback the database transaction (Minitest::Test#teardown)
It is somewhat common for folks to use #setup to create objects for use in tests. After the test method completes the test object is garbage collected, so most folks don't use #teardown to clean up after the test. Because of this #teardown is typically a more advanced feature that you don't normally use when writing tests. I see it used much more often in testing libraries that enhance Minitest.
But there are times I do use #teardown in my tests. Here is an example of when I might use it.
require "minitest/autorun"
class Foo
def initialize namer
#namer = namer
end
def name
#namer.name
end
end
class FooTest < Minitest::Test
def setup
#namer_mock = Minitest::Mock.new
#namer_mock.expect :name, "foo"
#foo = Foo.new #namer_mock
end
def test_name
assert_equal "foo", #foo.name
end
def teardown
#namer_mock.verify
end
end
I'm trying to mock Date.today.wday in a rake task in rspec.
Gem versions: RSpec 2.14.8 --- Rails 4.1.1 --- ruby 2.0.0
Here is a simplified fake version of my test to illustrate essentially what I'm trying to do:
describe "scheduler" do
describe ":thursday_invitations" do
let(:run_issue_invites) do
Rake::Task[:thursday_invitations].reenable
Rake.application.invoke_task :thursday_invitations
end
before do
Rake.application.rake_require 'tasks/scheduler'
Rake::Task.define_task(:environment)
Date.today.should_receive(:wday).and_return(4) ###MY NEMESIS CODE LINE
end
context "on thursday" do
it "issues invitations" do
expect(Date.today.wday).to eq(4) ###THE VERIFICATION TEST THAT KEEPS FAILING
run_issue_invites
expect(<other_stuff_to_test>).to <return_properly>
end
end
end
end
So, the real key of this is mocking out the Date.today.wday. Because I want to be able to run my spec on any day of the week, I need to mock/stub out this method to always return "4" (the day-number for Thursday in Rails). So, I initially setup my test to first verify that it is receiving a "4" (the assertion in the test). If today is, say, Friday (which it is) and I run the test, it fails saying that it expected "4" but got "5". That is, it is not returning the value that I want it to when I receive the method. I have tried stubbing with similar ineffective results. Normally, mocking is a breeze, but what seems to be the hangup is .wday which operates on Date.today.
Because this is a rake task (which I'm not as familiar with mocking), I may have to specify something further, but I haven't been able to get to the bottom of it...
Let me know if you need any other clarifying information.
I believe the reason you're not seeing the behavior you expect is that the object you are mocking is the not the same object under test.
In a Rails 4+ environment, this is what I see on the rails console:
[1]> Date.today.object_id
70104549170200
[2]> Date.today.object_id
70104552970360
The fact that the object_id is different in subsequent calls to Date.today means that each call returns a new object. So Date.today.should_receive(:wday).and_return(4) is setting an expectation on an object that will never be used again.
You'll need to rewrite your spec to ensure the same object is returned by Date.today each time. Here's one solution, omitting other parts of your example for clarity:
let!(:today) { Date.today }
before do
Date.stub(:today).and_return(today)
today.should_receive(:wday).and_return(4)
end
it "issues invitations" do
expect(Date.today.wday).to eq(4)
end
I have a unit test in Rails, the model it is testing uses Sequel for our own internal reasons, but the test data is created using factory_girl which seems to wrap the whole test in an activerecord transaction. I can use self.use_transactional_fixtures = false but was wondering if there was a way to not leave the test datain my db after my test is done.
I'm not sure what test library you are using. For RSpec 1, the following runs each spec example in its own transaction:
class Spec::Example::ExampleGroup
def execute(*args, &block)
x = nil
Sequel::Model.db.transaction{x = super(*args, &block); raise Sequel::Error::Rollback}
x
end
end
For RSpec 2, you can use an around filter to accomplish the same thing. I'm not sure how to do it in test/unit, you probably need to override Test::Unit::TestCase#run.
I've got a functional test that's using fixtures. I also am using fixtures in my Unit tests, but they work without flaw. When running the functional tests, I get a:
NoMethodError: undefined method 'recycle!' for #<Response:0x10346be10>
/test/functional/responses_controller_test.rb:10:in 'test_testing'
My functional tests, at this point, are doing nothing more than a get to the index action. Example:
setup do
#response = responses(:one)
end
test "testing" do
get :index
assert true
end
My TestHelper class does include all fixtures, so the Responses fixtures are definitely loading. And like I said, the fixtures work perfectly fine in Unit tests.
Any idea what might be causing this?
Change
setup do
#response = responses(:one)
end
to
setup do
#myresponse = responses(:one)
end
and off you go!
The problem lies in "actionpack-3.0.1/lib/action_controller/test_case.rb" around line 386.
The testcase assumes that #response holds a "ActionDispatch::Response". You overwrite it, it's no "ActionDispatch::Response" anymore and the testcase fails.
I'm not sure if this is intended behaviour.
Anyway, just make sure you don't overwrite #response/#request/#controller/#routes and it should work.
Flo's answer is mostly correct, but I just wanted to clarify:
In Rails 3.0.x that #request is expected to be an ActionController::TestRequest type, which has the recycle! method defined on it. There's TestResponse, TestRequest, and TestSession.
You might want to actually initialize or manipulate objects of these types for certain situations. In my case, I needed to mock the site having a specific domain name with
#request = ActionController::TestRequest.new unless #request
#request.host = "www.hostname.com"
I just had a similar problem naming a variable #request.
I changed to #_request and it solved the problem.