Recipe 7.20. Writing Custom AssertionsProblemAs your test suite grows, you find that you need assertions that are specific to your applications. You can, of course, create the tests you need with the standard assertions (it's just code), but you'd rather create custom assertions for tests that you use repeatedly. There's no need to repeat yourself in your tests. Solution
Define a method in
test_helper.rb
. For example, you might find that you're writing many test
test/test_helper.rb :
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
class Test::Unit::TestCase
self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
def assert_valid_isbn(isbn)
assert(/^\d{9}[\dxX]$/.match(isbn.to_s), "ISBN is invalid")
end
end
You can now use your custom assertion in any of your tests. test/unit/book_test.rb : require File.dirname(__FILE__) + '/../test_helper' class BookTest < Test::Unit::TestCase fixtures :books def test_truth assert_valid_isbn(1111111) end end Discussion
assert_valid_isbn
is a wrapper around the
assert
method. The method body asserts that the argument passed in matches the
Regexp
object defined between by the contents of "//". If the
match
method of
Regexp
returns a
MatchData
object, the assertion succeeds.
The solution
Even if you don't anticipate the code in your assertions to change, custom assertions can avoid code duplication. If you've got an assertion that contains complex logic, use
assert_block
method of the
Test::Unit::Assertions
module to test whether a block of code yields
TRue
or not.
assert_block
takes an error message as an argument and is passed a block of code to be
assert_block(message="assert_block failed.") { ...}
See Also
|
Recipe 7.21. Testing File UploadProblemContributed by: Evan Henshaw-Plath (rabble) Your have an application that processes files submitted by users. You want a way to test the file-uploading functionality of your application as well as its ability to process the contents of the uploaded files. Solution
You have a controller that accepts files as the
:image
param and
app/controllers/image_controller.rb :
def upload
@image = Image.new(params[:image])
if @image.save
notice[:message] = "Image Uploaded Successfully"
else
notice[:message] = "Image Upload Failed"
end
end
Your Image model schema is defined by:
ActiveRecord::Schema.define() do
create_table "images", :force => true do t
t.column "title", :string, :limit => 80
t.column "path", :string
t.column "file_size", :integer
t.column "mime_type", :string
t.column "created_at", :datetime
end
end
The
Image
model has an attribute for
image_file
but is added manually and will not be written in to the database. The model stores only the path to the file, not its contents. It writes the
File
object to a actual file in the
./public/images/
directory and it
app/model/image_model.rb :
class Image < ActiveRecord::Base
attr_accessor :image_file
validates_presence_of :title, :path
before_create :write_file_to_disk
before_validation :set_path
def set_path
self.path = "#{RAILS_ROOT}/public/images/#{self.title}"
end
def write_file_to_disk
File.open(self.path, 'w') do f
f.write image_file.read
end
end
end
To test uploads, construct a post where you pass in a mock file object, similar to what the Rails libraries do internally when a file is received as part of a post: test/functional/image_controller_test.rb :
require File.dirname(__FILE__) + '/../test_helper'
require 'image_controller'
# Re-raise errors caught by the controller.
class ImageController; def rescue_action(e) raise e end; end
class ImageControllerTest < Test::Unit::TestCase
def setup
@controller = ImageController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_file_upload
post :upload, {
:image => {
:image_file => uploadable_file('test/mocks/image.jpg',
'image/jpeg'),
:title => 'My Test Image'
}
}
assert_kind_of? Image, assigns(:image),
'Did @image get created with a type of Image'
assert_equal 'My Test Image', assigns(:image).title,
'Did the image title get set?'
end
end
You must create a mock file object that simulates all the
Next, create the following helper method that will be available to all your tests: test/test_helper.rb :
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
class Test::Unit::TestCase
self.use_transactional_fixtures = true
def uploadable_file( relative_path,
content_type="application/octet-stream",
filename=nil)
file_object = File.open("#{RAILS_ROOT}/#{relative_path}", 'r')
(class << file_object; self; end;).class_eval do
attr_accessor :original_filename, :content_type
end
file_object.original_filename =
File.basename("#{RAILS_ROOT}/#{relative_path}")
file_object.content_type = content_type
return file_object
end
end
DiscussionRails adds special methods to the file objects that are created via an HTTP POST. To properly test file uploads you need to open a file object and add those methods. Once you upload a file, by default, Rails places it in the /tmp/ directory. Your controller and model code will need to take the file object and write it to the filesystem or the database. File uploads in Rails are passed in simply as one of the parameters in the params hash. Rails reads in the HTTP POST and CGI parameters and automatically creates a file object. It is up your controller to handle that file object and write it to a file on disk, place it in the database, or process and discard it.
The convention is that you store files for tests in the
./test/mocks/test/
directory. It's important that you have routines that clean up any files that are saved locally by your tests. You should add a
The following example shows how you can add a custom clean-up method, which deletes any image files you may have previously uploaded.
teardown
, like
setup
, is called for each test method in the class. We know from the above that all images are getting written to the
./public/images/
directory, so we just need to delete everything from that directory after each test.
teardown
is run regardless of whether the test
test/functional/image_controller_test.rb :
def teardown
FileUtils.rm_r "#{RAILS_ROOT}/public/backup_images/", :force => true
FileUtils.mkdir "#{RAILS_ROOT}/public/backup_images/"
end
See Also
|