Hi folks,
This is my first post. Thanks for all the fantastic content. I’ve been learning a lot.
Context:
I’ve being implementing an abstract calendar interface which will require several implementations. The abstract interface represents the operations that I would like in one gem, each implementation is in its own isolated gem. (For example: gem ‘calendar’ and gem ‘calendar-outlook’.)
I have included a sketch of the code below. I have based myself on the structure observed in the ‘omniauth’ and ‘omniauth-facebook’ gems for example, which uses a module to act as a container for provided implementations. (With this approach one can then use Calendar::Implementations.constants and Calendar::Implementations.const_get to build out a Calendar::Factory.)
Question:
This is all working very well for me. So here is my real question:
How can I write a set of rspec tests once in the ‘calendar’ gem and have them run against the ‘calendar-outlook’ (instance of) while I’m working on the implementation gem.
Basically, I need to run two sets of tests:
(1) The ‘internal’ specs from the ‘calendar-outlook’ gem.
(2) Create an instance of Calendar::Implementations::Outlook in the ‘calendar-outlook’ gem and test it against the api specs provided in the ‘calendar’ gem.
Has anybody got any ideas as how to approach this?
Current Approach:
My current approach is very ugly: The ‘calendar’ gem instantiates all implementations (each implementation provides a mock credential) and runs the tests against each implementation (I even do an each block in every test… This is horrible!) I don’t want to be running the specs of the abstract gem when I’m working on the implementation and it also requires the abstract gem to know about the implementations which is far from ideal. This creates a kind of circular reference which I really, really don’t feel comfortable with. (I’m surprised this worked actually.) I’m sure there are many more issues with this approach but I guess it’s pretty clear that this is not clean.
There you go, I’d be very happy to hear if anybody has some suggestions!
Thanks in advance,
Brian.
require 'singleton'
# gem 'calendar'
module Calendar
class Credentials
def initialize(interface:, url:, token:, name:, password:)
...
end
end
class Base
def initialize(credentials)
@credentials = credentials
end
attr_accessor :credentials
end
module Implementations
# NOTE: Add your implementation to this module and it will be picked up automatically
# class [Implementation_Name] < Calendar::Base
# def initialize(credentials) ... super ... end
# def make_reservation ... end
# ...
# end
end
class Factory
include Singleton
def implementations
Implementations.constants
end
def fetch(credentials)
implementation = Implementations.const_get(credentials.interface.to_sym)
implementation.new(credentials)
end
end
end
# gem 'calendar-outlook'
module Calendar
module Implementations
class Outlook < Calendar::Base
def initialize(credentials)
super
@connection = self.object_id
end
def make_reservation
# Put a real implementation in here
end
end
end
end
# gem 'calendar-google'
module Calendar
module Implementations
class Google_Calendar < Calendar::Base
# Another Implementation
end
end
end
credentials = Calendar::Credentials.new(
interface: interface,
url: "http://server.com",
token: "",
name: "fatherted",
password: "1234"
)
calendar ||= Calendar::Factory.instance.fetch(calendar_credentials)
calendar.make_reservation(...)