Code Monkey home page Code Monkey logo

clockwork-test's Introduction

Clockwork::Test

Build Status

Clockwork is a scheduler process for running scheduled jobs. These scheduled jobs are likely of critical importance to your application. You need to ensure that the jobs run when they should, as often as they should, and with the proper behavior when run.

Clockwork::Test includes additional functionality that makes testing of your clock.rb file easy. This gem can help make sure that you have scheduled your events appropriately, including testing if: or at: conditions, as well as allowing you to run assertions against the code that is executed when a job is run.

Clockwork::Test runs its test suite against various versions of ruby, which are documented in the GitHub Actions configuration.

Clockwork::Test does not require any specific test framework to be used with it, though all examples will use rspec.

Installation

Add this to your application's Gemfile:

group :test do
  gem 'clockwork-test'
end

And then execute:

$ bundle

Or install it yourself as:

$ gem install clockwork-test

Example Tests

Given the following clock.rb file:

require "clockwork"

module Clockwork
  configure do |config|
    config[:tz] = "US/Eastern"
  end

  handler { |job| logger.info "Running #{job}" }

  every(1.minute, "Run a job") do
    "Here's a running job"
  end
end

The following tests may be run against it:

require "clockwork/test"

describe Clockwork do
  after(:each) { Clockwork::Test.clear! }

  it "runs the job once" do
    Clockwork::Test.run(max_ticks: 1, file: "./clockwork.rb")

    expect(Clockwork::Test.ran_job?("Run a job")).to be_truthy
    expect(Clockwork::Test.times_run("Run a job")).to eq 1
    expect(Clockwork::Test.block_for("Run a job").call).to eq "Here's a running job"
  end

  it "runs the job every minute over the course of an hour" do
    start_time = Time.new(2015, 11, 2, 2, 0, 0)
    end_time = Time.new(2015, 11, 2, 3, 0, 0)

    Clockwork::Test.run(start_time: start_time, end_time: end_time, tick_speed:
      1.minute, file: "./clockwork.rb")

    expect(Clockwork::Test.times_run("Run a job")).to eq 60
  end

  describe "RSpec Custom Matcher" do
    subject(:clockwork) { Clockwork::Test }

    before { Clockwork::Test.run(max_ticks: 1, file: "./clockwork.rb") }

    it { should have_run("Run a job") }
    it { should have_run("Run a job").once }

    it { should_not have_run("Run a job").exactly(2).times }
  end
end

Usage

Replacing any calls to Clockwork with Clockwork::Test in your tests should perform as expected, with the one caveat that a job running in Clockwork::Test will not actually execute its event's handler code. That is suppressed, but made available via the block_for method.

Running Clockwork in Tests

A call to Clockwork.run will run the clockwork process until it is signaled to stop, such as by receiving the SIGINT signal. Clockwork::Test.run provides two ways to easily stop the current execution:

  1. After a particular number of clock ticks (See the max_ticks configuration setting).
  2. After a moment in time has been reached (See the end_time configuration setting).

Either of these methods can be used by passing the proper configuration option into the run method of Clockwork::Test. If both are provided, whichever occurs first will be used to halt execution.

Configuration Settings

max_ticks

Setting the max_ticks will determine the number of times that clockwork will run any events scheduled at that time. This is commonly used to let clockwork execute once, to then see if particular jobs are triggered right when clockwork starts up.

start_time

This is the time that you would like the clock file to start processing it. This may be used with either the max_ticks or end_time option to halt execution of the run loop.

If the start_time option is used, Time.now will be modified to the start_time at the beginning of execution of Clockwork::Test.run, and will be returned to the proper time at the conclusion of the run.

end_time

This specifies the time at which execution of clockwork should terminate. The run loop will continue until the end_time is reached, provided the max_ticks has not been exceeded, if that is provided.

This is commonly used along with start_time and tick_speed to quickly see if jobs run as often as expected over a certain period of time.

When start_time is set, this setting defaults to Time.current.

tick_speed

tick_speed should be provided as a unit of time, such as 1.minute or 3.hours. It is the amount of time that the test will progress time to on every subsequent clock tick.

If tick_speed is set to 1.minute and the current time is 13:00, the first tick will occur at 13:00, and the second will appear occur at 13:01.

This can be used in concert with start_time and end_time to quickly simulate moving across a large period of time, without having to wait for the full time period to occur in reality.

Note that tick_speed should not be set any higher than the minimum amount of granularity used in the clock file under test. Should the tick_speed be set to a higher amount, the number of times a job is run, or if the job is run at all, is subject to inconsistent and inaccurate results.

For example, with a tick_speed of 30.minutes and a job that runs every 1.minute over the course of an hour, the times_ran for that job will not be 60. It will be 2, assuming minimal passage of time in evaluating the actual clock tick.

file

The location of the clock file to test. This defaults to "./config/clock.rb" if a value is not provided.

Assertions Against Test Runs

After running your clock file with Clockwork::Test.run, you can use the following methods to assert the clock file ran as expected:

ran_job?

ran_job? lets you know if a particular job ran during previous executions of Clockwork::Test.run.

If the job "test_job" has run, ran_job?("test_job") will be true; otherwise, the method will return false.

times_run

times_run provides the number of times a job has run during previous executions of Clockwork::Test.run.

If the job "test_job" never ran, times_run("test_job") will be 0; otherwise, it will be the number of times that the event was executed.

block_for

block_for returns the block that would be handled and run on each execution of a job. This can be useful for ensuring that the block you associated with an event does whatever it is that you expect it to do.

If the job "test_job" never ran, block_for("test_job") will return an empty proc; otherwise, the block of code that the event would run is returned, which can then be called to test.

RSpec Matchers

Clockwork::Test includes a rspec test matcher to check if a job has been run.

To use, add the following to your spec_helper.rb:

RSpec.configure do |config|
  config.include(Clockwork::Test::RSpec::Matchers)
end

The have_run matcher checks if a job by the name provided has been run, and allows for a specific number of times to be checked against.

expect(Clockwork::Test).to have_run("Job Name").exactly(42).times

You can alternatively pass the number of times in as an optional argument in the have_run method, as such:

expect(Clockwork::Test).to have_run("Job Name", times: 42)

Chaining once to have_run will check that the job was run one time only.

If you do not pass the number of times, either with a chained method call or as an optional argument to have_run, the matcher will just check if the job was run at all. It provides no assertion as to how often it was run, just that it's at least once.

Resetting the clock

After each invocation of Clockwork::Test.run and assertions made against it, Clockwork::Test.clear! should be called. This will wipe all history away and load up the clock file fresh on the next run. Failure to do so across tests will carry over past history, which will cause the number of times a job has run to be incorrect and subject to a test ordering bug.

Contributing

  1. Fork it ( https://github.com/kevin-j-m/clockwork-test/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Run the tests (rspec) and ensure they pass
  4. Commit your changes (git commit -am 'Add some feature')
  5. Push to the branch (git push origin my-new-feature)
  6. Create a new Pull Request
  7. Add a new entry to the CHANGELOG.md in the Current Release section. Include a description of the change, the PR, and your name.

clockwork-test's People

Contributors

aprescott avatar bencolon avatar brain-geek avatar felix-dumit avatar kevin-j-m avatar rahul342 avatar robinbrandt avatar tiendo1011 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

clockwork-test's Issues

Release new version

Hey, @kevin-j-m , could you release a new version of gem?

There are quite some changes in changelog and it would be awesome to have those on rubygems.

Thanks!

Clockwork::DatabaseEvents

I'm using database backed events in a clockwork implementation, and I'm trying use this gem with rspec. However, nothing happens. It seems that the event getting fetched from the database, and not using the correct every that this gem has overridden. It seems like the current version of ClockworkTest is not compatible with database backed events. If I'm wrong, is there a different setup for this gem that is not documented when using database backed events.

Migrating from Timecop to ActiveSupport time travel

For quite some time, active support has had support for the same functionality Timecop provides.

Furthermore, this project already has dependency on AS, as clockwork is dependent on it.

Would it make sense to remove this dependency?

Also, this will require major release afterwards.

Forces `config/clock.rb`

If location of clock.rb is not config, specs fail. Shouldn't this be more flexible?

Failures:

  1) Clockwork runs the test one time
     Failure/Error: Clockwork::Test.run(max_ticks: 1)

     LoadError:
       cannot load such file -- ./config/clock.rb
     # ./spec/clock_spec.rb:7:in `block (2 levels) in <top (required)>'

times_run doesn't count when using at: option

Here is my clockwork file:

  every(1.day, "available pl margin updater job", :at => "00:00") do
    AvailablePlMarginUpdaterJob.perform_later
  end

And here is the spec:

    it "runs AvailablePlMarginUpdaterJob every day over the course of six days" do
      Time.use_zone("Asia/Bangkok") do
        start_time = Time.zone.local(2015, 11, 2, 23, 0, 0)
        end_time = Time.zone.local(2015, 11, 7, 23, 59, 0)
        Clockwork::Test.run(start_time: start_time, end_time: end_time, tick_speed: 1.day)
        expect(Clockwork::Test.times_run("available pl margin updater job")).to eq 5
      end
    end

If I run the test, it failed with:

     Failure/Error: expect(Clockwork::Test.times_run("available pl margin updater job")).to eq 5

       expected: 5
            got: 0

It runs ok if I remove the at: option from the clockwork file.
I follow the spec files in this repo but couldn't find any more info to help with this problem.
Thank you very much for your help, I really appreciate that.

Support for Database Events

I've got a bunch of clockwork tasks which are implemented as database events, as per the docs https://github.com/tomykaira/clockwork#use-with-database-events.

require 'clockwork'
require 'clockwork/database_events'
require File.expand_path('../../config/boot',        __FILE__)
require File.expand_path('../../config/environment', __FILE__)

module Clockwork

    # required to enable database syncing support
    Clockwork.manager = DatabaseEvents::Manager.new

    # Update all mailchimp publishers with the latest
    # details from the Mailchimp account.
    sync_database_events model: PublisherSubscriberSyncClock, every: 1.minute do |model_instance|
        # Sync the unsubscribers for the publisher
        model_instance.delay(:queue => 'publisher-subscriber-sync').sync_subscribers if model_instance.responds_to?(:sync_subscribers)
    end

end

And I'm trying to test against them in the following manor.

# Stage an email publisher in the database.
before(:each) { FactoryGirl.create_list(:email_publisher, 3) }

# Run the clock.
before(:each) { Clockwork::Test.run(file: "#{Rails.root}/lib/clock.rb", max_ticks: 10, tick_speed: 1.minute) }

it "Ran the sync job" do
    # Assert that the job was run.
    expect(Clockwork::Test.ran_job?('PublisherSubscriberSyncClock')).to be_truthy
end

# Reset the tests after each has run.
after(:each) { Clockwork::Test.clear! }

However these clocks don't appear to be run and my assertions fail. Even when inspecting the job history through Clockwork::Test.manager.send(:history).jobs they are not listed anywhere, the history is empty.

Traditional non-database jobs are run as expected however.

Redefining constants

Perhaps due to the way it is requiring the clockwork configuration file:

config/clock.rb:9: warning: previous definition of EVERY_TEN_FULL_MINUTES was here

Load up test events by reading file

Rather than overloading the every and configure method call in Clockwork itself, Clockwork::Test#run should parse the file itself to hand off events to Clockwork::Test::Manager to register.

Test handler as well as schedule

Right now Clockwork::Test can check if the schedule that's been written is performing as expected. This should be extended to test if the handler that's written to run at that particular schedule is also working correctly.

Wildcards for times not supported?

EVERY_TEN_FULL_MINUTES = [
  "**:00",
  "**:10",
  "**:20",
  "**:30",
  "**:40",
  "**:50"
].freeze

every(1.hour, TestJob.name, at: EVERY_TEN_FULL_MINUTES, tz: "UTC") { }
# start at beginning of day to ensure we match the ten full minutes
now = Time.current.in_time_zone("UTC")
start_at = now.beginning_of_day

Clockwork::Test.run(max_ticks: 3, start_at: start_at, tick_speed: 30.minutes)

assert_equal(2, Clockwork::Test.times_run(TestJob.name))
rails test test/config/clockwork_test.rb:21
Running via Spring preloader in process 20916
Run options: --seed 52425

# Running:

F

Failure:
ClockworkTest#test_test_job [test/config/clockwork_test.rb:28]:
Expected: 2
  Actual: 0

Testing 2 clock instances

I have 2 clock instances (2 clock.rb files: clock.rb && clock_business_logic_xyz.rb). Everything
runs OK but I am not sure how to rspec test both of them.

All tests are checking only tasks in clock.rb file.

Any ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.