Skip to content
Browse files

Updated to testing mailers section in the testing guide.

  • Loading branch information...
1 parent 4d25326 commit c02a70bdc313bca44f5735f8b0167b5cf82b413e @whiteboardmonk whiteboardmonk committed Oct 12, 2008
Showing with 34 additions and 105 deletions.
  1. +34 −105 railties/doc/guides/testing_rails_applications/testing_rails_applications.txt
View
139 railties/doc/guides/testing_rails_applications/testing_rails_applications.txt
@@ -754,8 +754,6 @@ Unit tests
In the unit tests, we run the mailer in isolation with tightly controlled inputs and compare the output to a known-value -- a fixture -- yay! more fixtures!
-==== Functional tests ====
-
In the functional tests we don't so much test the minute details produced by the mailer, instead we test that our controllers and models are using the mailer in the right way. We test to prove that the right email was sent at the right time.
=== Unit Testing ===
@@ -764,143 +762,74 @@ In order to test that your mailer is working as expected, we can use unit tests
==== Revenge of the fixtures ====
-For the purposes of unit testing a mailer, fixtures are used to provide an example of how output ``should'' look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory from the other fixtures. Don't tease them about it though, they hate that.
+For the purposes of unit testing a mailer, fixtures are used to provide an example of how output ``should'' look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory from the other fixtures. The name of the directory withing `test/fixtures` directly corresponds to the name of the mailer. So, for a mailer named UserMailer, its fixtures should reside in `test/fixtures/user_mailer` directory.
-When you generated your mailer (you did that right?) the generator created stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself.
+When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself.
==== The basic test case ====
-Here is an example of what you start with.
+Let's look at a unit test to test a mailer `UserMailer` whose action `invite` is used to send an invitation to a friend. It is an adapted version of the base test generated by the generator for an `invite` action.
[source, ruby]
-------------------------------------------------
-require File.dirname(__FILE__) + '/. ./test_helper'
-
-class MyMailerTest < Test::Unit::TestCase
- FIXTURES_PATH = File.dirname(__FILE__) + '/. ./fixtures'
-
- def setup
- ActionMailer::Base.delivery_method = :test
- ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries = []
-
- @expected = TMail::Mail.new
- end
+require 'test_helper'
- def test_signup
- @expected.subject = 'MyMailer#signup'
- @expected.body = read_fixture('signup')
+class UserMailerTest < ActionMailer::TestCase
+ tests UserMailer
+ def test_invite
+ @expected.from = 'me@example.com'
+ @expected.to = 'friend@example.com'
+ @expected.subject = "You have been invited by #{@expected.from}"
+ @expected.body = read_fixture('invite')
@expected.date = Time.now
- assert_equal @expected.encoded, MyMailer.create_signup(@expected.date).encoded
+ assert_equal @expected.encoded, UserMailer.create_invite('me@example.com', 'friend@example.com', @expected.date).encoded
end
- private
- def read_fixture(action)
- IO.readlines("#{FIXTURES_PATH}/my_mailer/#{action}")
- end
end
-------------------------------------------------
-The `setup` method is mostly concerned with setting up a blank slate for the next test. However it is worth describing what each statement does
-
-[source, ruby]
--------------------------------------------------
-ActionMailer::Base.delivery_method = :test
--------------------------------------------------
-
-sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (ActionMailer::Base.deliveries).
-
-[source, ruby]
--------------------------------------------------
-ActionMailer::Base.perform_deliveries = true
--------------------------------------------------
+`@expected` is an instance of `TMail::Mail` which we can use in our tests. It is defined in `ActionMailer::TestCase` . In the test above we use `@expected` to construct an email which we then assert with email created by our custom mailer.
-Ensures the mail will be sent using the method specified by ActionMailer::Base.delivery_method, and finally
+The `invite` fixture is the body of the email and is used as the sample content to assert against. The helper `read_fixture` is used to read in the content from this file.
-[source, ruby]
--------------------------------------------------
-ActionMailer::Base.deliveries = []
+Content of the `invite` fixture:
+[source, text]
-------------------------------------------------
+Hi friend@example.com,
-sets the array of sent messages to an empty array so we can be sure that anything we find there was sent as part of our current test.
+You have been invited.
-However often in unit tests, mails will not actually be sent, simply constructed, as in the example above, where the precise content of the email is checked against what it should be. Dave Thomas suggests an alternative approach, which is just to check the part of the email that is likely to break, i.e. the dynamic content. The following example assumes we have some kind of user table, and we might want to mail those users new passwords:
-
-[source, ruby]
+Cheers!
-------------------------------------------------
-require File.dirname(__FILE__) + '/../test_helper'
-require 'my_mailer'
-class MyMailerTest < Test::Unit::TestCase
- fixtures :users
- FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
- CHARSET = "utf-8"
+This is the right time to understand a little more about writing tests for your mailers. The line `ActionMailer::Base.delivery_method = :test` in `config/environments/test.rb` sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (ActionMailer::Base.deliveries).
- include ActionMailer::Quoting
-
- def setup
- ActionMailer::Base.delivery_method = :test
- ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries = []
-
- @expected = TMail::Mail.new
- @expected.set_content_type "text", "plain", { "charset" => CHARSET }
- end
-
- def test_reset_password
- user = User.find(:first)
- newpass = 'newpass'
- response = MyMailer.create_reset_password(user,newpass)
- assert_equal 'Your New Password', response.subject
- assert_match /Dear #{user.full_name},/, response.body
- assert_match /New Password: #{newpass}/, response.body
- assert_equal user.email, response.to[0]
- end
-
- private
- def read_fixture(action)
- IO.readlines("#{FIXTURES_PATH}/community_mailer/#{action}")
- end
-
- def encode(subject)
- quoted_printable(subject, CHARSET)
- end
-end
--------------------------------------------------
-
-and here we check the dynamic parts of the mail, specifically that we use the users' correct full name and that we give them the correct password.
+However often in unit tests, mails will not actually be sent, simply constructed, as in the example above, where the precise content of the email is checked against what it should be.
=== Functional Testing ===
-Functional testing involves more than just checking that the email body, recipients and so forth are correct. In functional mail tests we call the mail deliver methods and check that the appropriate emails have been appended to the delivery list. It is fairly safe to assume that the deliver methods themselves do their job -- what we are probably more interested in is whether our own business logic is sending emails when we expect them to. For example the password reset operation we used an example in the previous section will probably be called in response to a user requesting a password reset through some sort of controller.
+Functional testing involves more than just checking that the email body, recipients and so forth are correct. In functional mail tests we call the mail deliver methods and check that the appropriate emails have been appended to the delivery list. It is fairly safe to assume that the deliver methods themselves do their job -- what we are probably more interested in is whether our own business logic is sending emails when we expect them to. For example the invite friend operation is sending an email appropriately.
[source, ruby]
----------------------------------------------------------------
-require File.dirname(__FILE__) + '/../test_helper'
-require 'my_controller'
-
-# Raise errors beyond the default web-based presentation
-class MyController; def rescue_action(e) raise e end; end
-
-class MyControllerTest < Test::Unit::TestCase
-
- def setup
- @controller = MyController.new
- @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
- end
-
- def test_reset_password
- num_deliveries = ActionMailer::Base.deliveries.size
- post :reset_password, :email => 'bob@test.com'
+require 'test_helper'
- assert_equal num_deliveries+1, ActionMailer::Base.deliveries.size
+class UserControllerTest < ActionController::TestCase
+ def test_invite_friend
+ assert_difference 'ActionMailer::Base.deliveries.size', +1 do
+ post :invite_friend, :email => 'friend@example.com'
+ end
+ invite_email = ActionMailer::Base.deliveries.first
+
+ assert_equal invite_email.subject, "You have been invited by me@example.com"
+ assert_equal invite_email.to[0], 'friend@example.com'
+ assert_match /Hi friend@example.com/, invite_email.body
end
-
end
----------------------------------------------------------------
== Guide TODO ==
* Describe _setup_ and _teardown_
* Examples for integration test
- * Updating the section on testing mailers
+ * Note about other popular testing approaches and plugins

0 comments on commit c02a70b

Please sign in to comment.
Something went wrong with that request. Please try again.