Practical 2

0. Finish Practical Sheet 1. Run the tests (rake test) to make sure everything works fine. If an error report claims that "MyString" is missing from the asset pipeline, find the file products.yml in the folder test/fixtures and change the value Mystring for the two image_url attributes in this file to ruby.jpg. In this way, test data refer to images that are actually contained in the folder app/assets/images.

1. Validation. Validators can be used in the model to automatically validate properties of data when entered into the data base. Rails allows to validate various aspects of data objects. A standard requirement on certain fields might be that they are filled in the first place. The presence of values in fields can be tested as follows. Add the following line to the product model (in the file …).

validates :title, :description, :image_url, presence: true

This will ensure the presence of data in the :title, :description, and :image_url fields. Try to enter a new product with any (or all) of these fields empty and watch the effect.

2. The list of products should not contain two items with the same title. The uniquness of values for a particular field can be tested as follows. Below the validator from the previous paragraph, add the line

validates :title, uniqueness: true

to the product model, then try and enter two products with the same title.

3. Validators are capable of ensuring very specific aspects of data. The following line

validates :price, numericality: { greater_than_or_equal_to: 0.01 }

when added to the product model, will not allow products to have a price of zero or less. Test it.

4. Finally, here is an example of a validator that uses a Ruby regular expression (%r{\.(gif|jpg|png)$}i) to specify that the URL for a product image ends with one of three possible extensions, lower or upper case.

validates :image_url, allow_blank: true, format: {
  with: %r{\.(gif|jpg|png)\Z}i,
  message: 'must be a URL for GIF, JPG or PNG image.'
}

It also specifies the error message to be used in case this rule is violated. Try it out.

4a. If all works fine, commit the changes to your local git repository, and push them to the github cloud.

git add .
git commit -m "added validations"
git push

5. Let’s make sure that after introducing all of these safety measures, the application is still working as expected. Enter the command

rake test

and study the report it produces. Hmmm … this used to go through without failures …. What could it be that broke the (automatically generated) unit tests?

6. The reported failures concern the product create and update actions. The testing of these actions, as specified in the file test/functional/products_controller_test.rb make use of a product called :one. Identify the places in that file which refer to this product.

The object is defined as a fixture in the file test/fixtures/products.yml. Fixtures are specially designed data for test purposes. They are formulated in the rather self-explaining YAML language. All those fixtures are loaded into a test database, before the actual tests are carried out.

The validators above require all products to have different titles, the fixtures don’t. Change the data so that the titles of one and two are different.

What else is wrong with product one (and two)? Fix this in the fixtures file and run the test suite to see if it succeeds.

7. Open the test/functional/products_controller_test.rb and study the failing test that "should create product". It looks like it tries to enter an exact copy of @product (which was created from the fixture one) into the database (on top of the fixture data already in there). Why is that bound to fail?

Replace the title of the new product (in the post) command by something like "New Book" and run the tests again until they all succeed.

7a. If all works fine, commit the changes to your local git repository, and push them to the github cloud:

git add .
git commit -m "fixed broken tests"
git push

8. Unit Testing. Rails provides tools to ensure that the application is working as expected. The technical term for this type of validation (which checks the software, rather than the data it deals with) is Unit Testing.

Unit tests reside in the (automatically generated) file test/models/product_test.rb. When you open the file you’ll find nothing but a placeholder. Insert the following test into the ProductTest class:

test "product attributes must not be empty" do
  product = Product.new
  assert product.invalid?
  assert product.errors[:title].any?
  assert product.errors[:description].any?
  assert product.errors[:price].any?
  assert product.errors[:image_url].any?
end

This test will create a new product, with empty components. Validation requires that all components are filled, therefore this product should currently be invalid. Specifically, it should have errors relating to each and every component. That’s what is tested here.

9. Testing the price. Each product should have a positive price. Formulate a further test (in product_test.rb) as follows.

test "product price must be positive" do
  product = products(:one)

  product.price = -1
  assert product.invalid?
  assert product.errors[:price].any?

  product.price = 0
  assert product.invalid?
  assert product.errors[:price].any?

  product.price = 1
  assert product.valid?
  assert product.errors[:price].none?
end

This checks that for invalid prices like -1 and 0, the product is invalid, and carries an error message. Do a rake test and see how it goes.

10. For the next test, we use a loop over a list of strings. In ruby, an array of words can be specified without quotes (or separating commas) by using the %w operator.

We can then use this functionality to test a variety of different filenames, some ok, some bad:

test "image url must point to an image file" do
  product = products(:one)
  ok = %w{ frog.gif frog.jpg frog.png FROG.PNG fRoG.PnG
           http://a.b.c/x/y/z/frog.png frog.jpeg }
  bad = %w{ frog.doc frog.png/less frog.png.less }

  ok.each do |url|
    product.image_url = url
    assert product.valid?, "#{url} should be a valid image url"
  end

  bad.each do |url|
    product.image_url = url
    assert product.invalid?, "#{url} shouldn't be a valid image url"
  end
end

11. We can add our own test data to the fixture files. Add

ruby:
  title: Programming Ruby 1.9 & 2.0
  description:
    Ruby is the fastest growing and most exciting dynamic language
    out there. If you need to get working programs delivered fast,
    you should add Ruby to your toolbox.
  image_url: ruby.jpg
  price: 49.95

to the product fixtures (which file?).

12. And we can refer to these fixtures by name (:ruby).

In the product model, there is a validator that looks after the uniqueness of product titles. In order to test whether that works, you can create a product that has the same title as a product already in the (test) database, and check whether saving of this product fails. Add the following test:

test "product is not valid without a unique title" do
  product = Product.new(:title       => products(:ruby).title,
                        :description => "xxx",
                        :price       => 1,
                        :image_url   => "barney.gif")

  assert product.invalid?
  assert_equal ["has already been taken"], product.errors[:title]
end

and then run the test again. (Note the logic behind this: if the product is invalid, this test succeeds!)

12a. Finally, if all the tests work as desired, commit the changes to your local git repository, and push them to the github cloud.

git status
git commit -a -m "added more unit tests"
git push

13. Any questions about the code in these examples? As evidence for your participation in this practical, add a comment below …

Written on October 21, 2020 by CS424.