How to use transactional fixtures in test unit - ruby-on-rails

When i run test cases, i don't want to store records in my databases. How can i achieve it.
Here is my code :-
class Sample << Test::Unit::TestCase
def setup
# code
end
def teardown
# code
end
def test_sample
# code
end
end
Am using the following gem:-
gem 'test-unit'
to run tests and api call for GET/POST/PUT/DELETE methods to create/delete records in database.

I assume what you mean is that the objects you are using are not persisted in a local database, but rather in a remote system accessed via an API. That is, when you save an objects attributes, they are sent to a remote server via an API call.
Have a look at webmock. It works well with test/unit and test/minitest (which is test/unit on steroids). Basically you define the http call that should result from an action, and pass that to webmock. Then when the action is tested webmock will intercept the http call, and return a mocked response. If the action call is different to the one you defined, web mock will generate an error that will cause the test to fail.
So say on creating a sample, you expect a POST to example.com/samples with a sample attribute :foo set to 'bar', you could write a test like this:
def test_create_sample
data = 'bar'
sample = Sample.new
sample.foo = data
stub_request(:post, "example.com/samples/1").
with(:body => {:sample => {foo: data}})
assert sample.save
end
This also assumes that your save action checks the api response, and returns true if everything is OK.

Related

How to mock a chain of methods in Minitest

It looks like the only source of information for stubbing a chain of methods properly are 10+ years ago:
https://www.viget.com/articles/stubbing-method-chains-with-mocha/
RoR: Chained Stub using Mocha
I feel pretty frustrated that I can't find information of how to do this properly. I want to basically mock Rails.logger.error.
UPDATE: I basically want to do something as simple as
def my_action
Rails.logger.error "My Error"
render json: { success: true }
end
And want to write a test like this:
it 'should call Rails.logger.error' do
post my_action_url
???
end
I think maybe you misunderstood the term chain of methods in this case, they imply the chain of ActiveRecord::Relation those be able to append another. In your case Rails.logger is a ActiveSupport::Logger and that's it. You can mock the logger and test your case like this:
mock = Minitest::Mock.new
mock.expect :error, nil, ["My Error"] # expect method error be called
Rails.stub :logger, mock do
post my_action_url
end
mock.verify
Beside that, I personally don't like the way they test by stub chain of method, it's so detail, for example: i have a test case for query top ten users and i write a stub for chain of methods such as User.where().order()..., it's ok at first, then suppose i need to refactor the query or create a database view top users for optimize performance purpose, as you see, i need to stub the chain of methods again. Why do we just treat the test case as black box, in my case, the test case should not know (and don't care) how i implement the query, it only check that the result should be the top ten users.
update
Since each request Rails internal call Logger.info then if you want ignore it, you could stub that function:
def mock.info; end
In case you want to test the number of method called or validate the error messages, you can use a spy (i think you already know those unit test concepts)
mock = Minitest::Mock.new
def mock.info; end
spy = Hash.new { |hash, key| hash[key] = [] }
mock.expect(:error, nil) do |error|
spy[:error] << error
end
Rails.stub :logger, mock do
post my_action_url
end
assert spy[:error].size == 1 # call time
assert spy[:error] == ["My Error"] # error messages
It's better to create a test helper method to reuse above code. You can improve that helper method behave like the mockImplementation in Jest if you want.

Rspec stubbing return values from a given block

I am trying to get to grips with stubbing in Rspec. I would like to understand how to stub returns values from an array.
Here is what I am attempting to stub at the moment,
if client.jobs.any?
client.jobs.map do |job|
if job.job_locations.any?
job.job_locations.map do |jl|
if jl.location_id == self.location_id
errors.add(:location_id, "This location is in use with another of the client's jobs")
return false
end
end
end
end
end
I can stub the first line (that there the client has jobs) but I am not sure how to stub the return values of the array so that they run in the spec tests.
here is the relevant snippet
context "location_id matches job_location location_id" do
before do
allow(client_location).to receive(:client).and_return(client)
allow(client).to receive(:jobs).and_return(some_jobs)
allow(some_jobs).to receive(:map).and_return([job])
#eventually I want to get to this
allow_any_instance_of(JobLocation).to receive(:location_id).and_return(1)
allow_any_instance_of(ClientLocation).to receive(:location_id).and_return(1)
end
it "returns false" do
expect(#instance.destroy).to be_falsey
end
end
I should add the 'some_jobs' variables in the spec test are factory girl generated instances. Some jobs would equal [some_jobs].
If you define some_jobs to be [job] (e.g. via RSpec's let mechanism), then you don't need to redefine map and your production code will continue to execute. By stubbing out some_jobs.map, you basically bypassed the rest of your production code.
As an aside, the definitions of client_location, client, some_jobs and job are all highly relevant to the code snippet you shared and should be included.

How do I stub a method for a class generated at run-time with Rails minitest?

I want to stub out a call to a 3rd party component, but finding this pretty challenging in Rails mini-test. I'll start with the most basic question above. Here is some very simplified pseudo code to better explain what I'm trying to do:
class RequestController < ActionController::Base
def schedule
# Parse a bunch of params and validate
# Generate a unique RequestId for this request
# Save Request stuff in the DB
# Call 3rd party scheduler app to queue request action
scheduler = Scheduler.new
submit_success = scheduler.submit_request
# Respond to caller
end
end
So I'm writing integration tests for RequestController and I want to stub out the 'scheduler.submit_request' call.
My test code looks something like this:
def test_schedule_request
scheduler_response = 'Expected response string for RequestId X'
response = nil
Scheduler.stub :submit_request, scheduler_response do
# This test method calls the RequestController schedule route above
response = send_schedule_request(TEST_DATA)
end
# Validate (assert) response
end
Seems pretty straightforward, but apparently I can't stub out a method for a class that doesn't exist (yet). So how do I stub out a class method for an object created at run-time in the code I'm testing?
I am not sure about minitest. But in rspec, you will have to stub the initialization part as well and return a mocked scheduler object
This is how i would do it in rspec.
mock_scheduler = double("scheduler")
Scheduler.stub(:new).and_return(mock_scheduler)
mock_scheduler.stub(:submit_request).and_return(response)
Other option would be
Scheduler.any_instance.stub(:submit_request).and_return(response)
As Vimsha noted you have to first stub out the class initialization. I couldn't get his code to work, but this revised test code below has the same idea:
def test_schedule_request
scheduler_response = 'Expected response string for RequestId X'
response = nil
scheduler = Scheduler.new
Scheduler.stub :new, scheduler do
scheduler.stub :submit_request, scheduler_response do
# This test method calls the RequestController schedule route above
response = send_schedule_request(TEST_DATA)
end
end
# Validate (assert) response
end
Thanks,
Dave

Rails: test a helper that needs access to the Rails environment (e.g. request.fullpath)

I have a helper that accesses request.fullpath. Within an isolated helper test, request is not available. What should I do? Can I somehow mock it or something like that?
I'm using the newest versions of Rails and RSpec. Here's what my helper looks like:
def item(*args, &block)
# some code
if request.fullpath == 'some-path'
# do some stuff
end
end
So the problematic code line is #4 where the helper needs access to the request object which isn't available in the helper spec.
Thanks a lot for help.
Yes, you can mock the request. I had a whole long answer here describing how to do that, but in fact that's not necessarily what you want.
Just call your helper method on the helper object in your example. Like so:
describe "#item" do
it "does whatever" do
helper.item.should ...
end
end
That will give you access to a test request object. If you need to specify a specific value for the request path, you can do so like this:
before :each do
helper.request.path = 'some-path'
end
Actually, for completeness, let me include my original answer, since depending on what you're trying to do it might still be helpful.
Here's how you can mock the request:
request = mock('request')
controller.stub(:request).and_return request
You can add stub methods to the returned request similarly
request.stub(:method).and_return return_value
And alternative syntax to mock & stub all in one line:
request = mock('request', :method => return_value)
Rspec will complain if your mock receives messages that you didn't stub. If there's other stuff Just call your request helper method on the helper object is doing that you don't care about in your test, you can shut rspec up by making the mock a "null object",example. like Like so
request = mock('request').as_null_object
It looks like all you probably need to get your specific test passing is this:
describe "#item" do
let(:request){ mock('request', :fullpath => 'some-path') }
before :each do
controller.stub(:request).and_return request
end
it "does whatever"
end
In a helper spec, you can access the request using controller.request (so controller.request.stub(:fullpath) { "whatever" } should work)

How can I test that my before_save callback does the right thing

I have a callback on my ActiveRecord model as shown below:
before_save :sync_to_external_apis
def sync_to_external_apis
[user, assoc_user].each {|cuser|
if cuser.google_refresh
display_user = other_user(cuser.id)
api = Google.new(:user => cuser)
contact = api.sync_user(display_user)
end
}
end
I would like to write an rspec test which tests that calling save! on an instance of this model causes sync_user to be called on a new Google instance when google_refresh is true. How could I do this?
it "should sync to external apis on save!" do
model = Model.new
model.expects(:sync_to_external_apis)
model.save!
end
As an aside, requesting unreliable resources like the internet during the request-response cycle is a bad idea. I would suggest creating a background job instead.
The usual method for testing is to ensure the results are as expected. Since you're using an API in this case that may complicate things. You may find that using mocha to create a mock object you can send API calls would allow you to substitute the Google class with something that works just as well for testing purposes.
A simpler, yet clunkier approach is to have a "test mode" switch:
def sync_to_external_apis
[ user, assoc_user ].each do |cuser|
if (Rails.env.test?)
#synced_users ||= [ ]
#synced_users << cuser
else
# ...
end
end
end
def did_sync_user?(cuser)
#synced_users and #synced_users.include?(cuser)
end
This is a straightforward approach, but it will not validate that your API calls are being made correctly.
Mocha is the way to go. I'm not familiar with rspec, but this is how you would do it in test unit:
def test_google_api_gets_called_for_user_and_accoc_user
user = mock('User') # define a mock object and label it 'User'
accoc_user = mock('AssocUser') # define a mock object and label it 'AssocUser'
# instantiate the model you're testing with the mock objects
model = Model.new(user, assoc_user)
# stub out the other_user method. It will return cuser1 when the mock user is
# passed in and cuser2 when the mock assoc_user is passed in
cuser1 = mock('Cuser1')
cuser2 = mock('Cuser2')
model.expects(:other_user).with(user).returns(cuser1)
model.expects(:other_user).with(assoc_user).returns(cuser2)
# set the expectations on the Google API
api1 - mock('GoogleApiUser1') # define a mock object and lable it 'GoogleApiUser1'
api2 - mock('GoogleApiUser2') # define a mock object and lable it 'GoogleApiUser2'
# call new on Google passing in the mock user and getting a mock Google api object back
Google.expects(:new).with(:user => cuser1).returns(api1)
api1.expects(:sync_user).with(cuser1)
Google.expects(:new).with(:user => cuser2).returns(api2)
api2.expects(:sync_user).with(cuser2)
# now execute the code which should satisfy all the expectations above
model.save!
end
The above may seem complicated, but it's not once you get the hang of it. You're testing that when you call save, your model does what it is supposed to do, but you don't have the hassle, or time expense of really talking to APIs, instantiating database records, etc.

Resources