2

Introduction

I have a hierarchy parent-children in my RoR project and I wrote a new feature to our ruby on rails project, that shows two random children records from a parent.

class Article < ActiveRecord::Base
  has_many :reviews

  def to_json
    {
      ...
      reviews: reviews.n_random.as_json
      ...
    }
  end
end

and

class Review < ActiveRecord::Base
  belongs_to :article

  scope :n_random, ->(n=2) { order("RANDOM()").limit(n) }
end

Now, the problem that I have is even though the randomness works correctly, even in tests, I have problems with few tests that actually test this feature indirectly.

Let's say that I have an ArticlesControllerTest test suite, that contains a method

test 'show renders correct article' do
    # given
  params = { format: :json, id: 1 } 
  article = Article.find(params[:id])

    # when
  post :get, params
  response_article = JSON.parse(response.body, symbolize_names: true)


    #then
  assert_response 200
  assert_equal response_article, article.to_json
end

Problem

The last assert_equal fails, because for example:

  • response_article contains ids 1, 2
  • article.to_json contains ids 1, 3

Question

Is it possible to write some kind of a filter, that makes postgres's RANDOM() return always constant value? I know that I can use SELECT setseed(0.5); to set seed, so that next SELECT RANDOM(); returns the same value (although the next RANDOM() will change), but what I would like to achieve is to do something like setseed(0.5) before every possible select from active records.

I'll gladly take any other responses that will help me with this problem, because I know that RoR and Postgres are two different servers and I have no idea how to test this randomness from postgres's side.

inb4: I don't want to modify tests in a huge way.

Maciej Pk
  • 123
  • 11

1 Answers1

1

You should probably use mocks / stubs for this, ensuring a consistent value just for the scope of this test. For example, with Mocha:

Article.any_instance.stubs(:to_json).returns({
  ...
  reviews: reviews.last(2).as_json,
  ...
})

Or

Review.expects(:n_random).returns(Review.last(2))

And, in this example, you can revoke these using, for example:

Article.any_instance.unstub(:to_json)

N.B. I'm not certain of the syntax for the :n_random stub on a class as I've not got the environment to test it, but hopefully you get the idea (source here).

This means, within your test you will see consistent data, overriding the RANDOM() ordering. That way you can test your controller is doing what's expected of it, without worrying about the random data being used outside of the test env.

To implement, simply include one of the above in your test, i.e.

test 'show renders correct article' do
  Review.expects(:n_random).returns(Review.last(2))

  # given
  params = { format: :json, id: 1 } 
  article = Article.find(params[:id])

  # when
  post :get, params
  response_article = JSON.parse(response.body, symbolize_names: true)


  #then
  assert_response 200
  assert_equal response_article, article.to_json
end
SRack
  • 11,495
  • 5
  • 47
  • 60
  • 1
    Thanks. I decided to add it to setup for everything with `Review.expects(:n_random).at_least(0).returns(Review.last(2))`. The reason why did I use `#at_least(0)` is because in my test example that I gave you, the `#n_random` is called at least twice and mocha failed with it. Nevertheless, thank you for the answer :) I would give you +1 if I had at least 15 points, but sadly I do not :C – Maciej Pk Jun 22 '18 at 17:49