#Testing
openhab-scripting
includes framework classes to allow you to write unit tests
for your openHAB rules written in JRuby. It loads up a limited actual openHAB runtime
environment. Because it is a limited environment, with no actual bindings or things,
you may need to stub out those actions in your tests. The autoupdate manager is
running, so any commands sent to items that aren't marked as autoupdate="false"
will
update automatically.
#Usage
You must run tests on a system with an actual openHAB instance installed, with your configuration. JRuby >= 9.3.8.0 must also be installed.
- Install and activate JRuby (by your method of choice - chruby, rbenv, etc.).
- Either create an empty directory, or use
$OPENHAB_CONF
itself (the former is untested) - Create a
Gemfile
with the following contents (or add to an existing one):
source "https://rubygems.org"
group(:test) do
gem "rspec", "~> 3.11"
gem "openhab-scripting", "~> 5.0"
gem "timecop"
end
group(:rules) do
# include any gems you reference from `gemfile` calls in your rules so that
# they'll already be available in the rules, and won't need to be
# re-installed on every run, slowing down spec runs considerably
end
- Run
gem install bundler
- Run
bundle install
- Run
bundle exec rspec --init
- Edit the generated
spec/spec_helper.rb
to satisfy your preferences, and add:
require "rubygems"
require "bundler"
Bundler.require(:default, :test)
require "openhab/rspec"
# if you have any automatic requires setup in jrubyscripting's config,
# (besides `openhab`), you need to manually require them here
- Create some specs! An example of
spec/switches_spec.rb
:
RSpec.describe "switches.rb" do
describe "gFullOn" do
it "works" do
GuestDownlights_Dimmer.update(0)
GuestDownlights_Scene.update(1.3)
expect(GuestDownlights_Dimmer.state).to eq 100
end
it "sets some state" do
rules["my rule"].trigger
expect(GuestDownlights_Scene.state).to be_nil
end
it "triggers a rule expecting an event" do
rules["my rule 2"].trigger(Struct.new(:item).new(GuestDownlights_Scene))
expect(GuestDownlights_Scene.state).to be_nil
end
end
end
- Run your specs:
bundle exec rspec
#Spec Writing Tips
- By default ruby files are looked for in
$OPENHAB_CONF/automation/ruby
and in$OPENHAB_CONF/automation/jsr223
. You can override and/or append to this by setting theopenhab_automation_search_paths
RSpec configuration setting in yourspec_helper.rb
. This can be useful to add staging directory for testing your rules.
RSpec.configure do |config|
config.openhab_automation_search_paths += "/my/staging/directory"
end
- See OpenHAB::RSpec::Helpers for all helper methods available in specs.
- All items are reset to NULL before each spec.
on_load
triggers are not honored. Items will be reset to NULL before the next spec anyway, so just don't waste the energy running them. You can still trigger rules manually.- Rule triggers besides item related triggers (such as cron or watchers) are not triggered. You can test them with trigger.
- You can trigger channels directly with OpenHAB::RSpec::Helpers#trigger_channel.
- Timers aren't triggered automatically. Use the OpenHAB::RSpec::Helpers#execute_timers
helper to execute any timers that are ready to run. The
timecop
gem is automatically included, so useTimecop.travel(5.seconds)
(for example) to travel forward in time and have timers ready to execute. Note that this includes implicit timers created by rules that use thefor:
feature. - Logging levels can be changed in your code. Setting a log level for a logger further up the chain (separated by dots) applies to all loggers underneath it.
OpenHAB::Log.logger("org.openhab.core.automation.internal.RuleEngineImpl").level = :debug
OpenHAB::Log.gem_root.level = :debug
OpenHAB::Log.root.level = :debug
OpenHAB::Log.events.level = :info
- Differing from when openHAB loads rules, all rules are loaded into a single JRuby execution context, so changes to globals in one file will affect other files. In particular, this applies to ids for reentrant timers will now share a single namespace among all files.
- Some actions may not be available; you should stub them out if you use them. Core actions like OpenHAB::Core::Actions#notify, OpenHAB::Core::Actions::Voice.say, and OpenHAB::Core::Actions::Audio.play_sound are stubbed to only log a message (at debug level).
- You may want to avoid rules from firing while setting up the proper state for a test. In that case, use the OpenHAB::RSpec::Helpers#suspend_rules helper.
- Item persistence is enabled by default using an in-memory store that only tracks changes to items.
- The OpenHAB::RSpec::Helpers#install_addon helper can be used to install an
addon like
binding-astro
if you need to be able to create things from your rules. Note that the addon isn't actually allowed to start, just be installed to make type metadata from XML available. - If you have any Things in your openHAB instance that take two minutes to come online due to missing type metadata, you can force them to initialize immediately by calling OpenHAB::RSpec::Helpers#initialize_missing_thing_types.
- You can add a
binding.irb
call in to a spec (or your rule file) to break execution at that point and allow you to explore the current state of things with a REPL.
#Configuration
There are a few environment variables you can set to help the gem find the necessary dependencies. The default should work for an OpenHABian install or installation on Ubuntu or Debian with .debs. You may need to customize them if your installation is laid out differently. Additional openHAB or Karaf specific system properties will be set the same as openHAB would.
Variable | Default | Description |
---|---|---|
$OPENHAB_HOME |
/usr/share/openhab |
Location for the openHAB installation |
$OPENHAB_RUNTIME |
$OPENHAB_HOME/runtime |
Location for openHAB's private Maven repository containing its JARs |
#Transformations
Ruby transformations must have a magic comment # -*- mode: ruby -*-
in them to be loaded.
Then they can be accessed as a method on OpenHAB::Transform based on the filename:
OpenHAB::Transform.compass("59 °")
OpenHAB::Transform.compass("30", param: "7")
OpenHAB::Transform::Ruby.compass("59 °")
They're loaded into a sub-JRuby engine, just like they run in openHAB.
#IRB
If you would like to use a REPL sandbox to play with your items, create bin/console with the following contents, and then run it:
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
Bundler.require
require "irb"
begin
require "openhab/rspec"
autorequires
load_rules
load_transforms
rescue => e
puts e.backtrace
raise
end
IRB.start(__FILE__)