How can I overriding class with proc and yield in

2019-08-07 03:35发布

I have below classes(only for example),

class Background
  def self.add_thread(&blcok)
    Thread.new do
      yield
      ActiveRecord::Base.connection.close
    end
  end
end

class Email
  def send_email_in_other_thread
    Background.add_thread do
      send_email
    end
  end
  def send_email
    UserMailer.greeting_email.deliver_now
  end
end

And below codes are for tests,

class EmailTest < ActiveSupport::TestCase
  class Background
    def self.add_thread(&block)
      yield
    end
  end

  test 'should send email' do
    assert_difference 'ActionMailer::Base.deliveries.size', 1 do
      send_email_in_other_thread
    end
  end
end

But this test fails, "ActionMailer::Base.deliveries.size" didn't change by 1. And 1 in about 20 times success.
I think it is because of the modified Background class. Maybe overriding in test doesn't work or yield proc is not executed instantly but in delayed.
I tried 'block.call' instead of yield, but the result is same.
How can I make this test always be success?

1条回答
一纸荒年 Trace。
2楼-- · 2019-08-07 03:59

This looks like a classic race condition. Thread.new returns as soon as the thread is spawned, not when it's work is completed.

Because your main thread doesn't halt execution, most of the time your assertion is run before the mail has been sent.

You could use the join method to wait for the sending thread to finish execution before returning, but then it would essentially be equivalent to a single thread again, as it blocks the calling (main) thread until work is done.

Thread.new do
  yield
  ActiveRecord::Base.connection.close
end.join

There are already some great gems, however, for dealing with background jobs in Rails. Check out SideKiq and Resque for example.

查看更多
登录 后发表回答