This is my first post. Thanks for all the fantastic content. I’ve been learning a lot.
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.)
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?
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,
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(...)