RSpec superclass mismatch (Sinatra)

68 Views Asked by At

I have several spec files for my Sinatra API. They test a Module MysuperBlog and a class API within that Module.

I have this in a RSpec config file :

RSpec.shared_context 'Reset class' do
    after(:each) do
        Object.send(:remove_const, :API)
    end
end

the spec files all have these lines of code:

require 'rack/test'
require 'json'
require_relative '../../../app/main.rb'

module MysuperBlog
    class API
        RSpec.describe 'An author logs in to his account', type: :request do

        include_context 'Reset class'

The main app file contains:

module MysuperBlog    
    class API < Sinatra::Base

Each spec succeeds but when I run rspec without arguments, it raises an error:

TypeError:
  superclass mismatch for class API

I guess this is caused by the module and the class being defined in each file.

How should I reorder the code so both the individual tests and the global test pass without this error?

1

There are 1 best solutions below

2
Stefan On

I guess this is caused by the module and the class being defined in each file.

The class keyword in Ruby serves two purposes: to create a class and to re-open an existing class.

If your class has a superclass other than Object, it has to be given when creating the class but it can be omitted when re-opening it, e.g.: (I'm using Array just for demonstration purposes, you shouldn't subclass core classes in general)

class Foo < Array ; end

class Foo ; end         # <- works

Doing it the other way round raises the error you are seeing:

class Foo ; end

class Foo < Array ; end # <- TypeError: superclass mismatch for class Foo

How should I reorder the code [...]

I assume that you load the file which is supposed to re-open an existing class before the file which is supposed to create the class and which specifies its superclass.

If so, ensure that you load the latter first (require it, e.g. in your spec helper) and your problem should be solved.


If you don't actually want to re-open an existing class but instead attempt to re-use the same class name for different throw-away classes in your tests, you can remove the constant via remove_const after you're done with the class / test: (e.g. in an RSpec after hook)

class Foo < Array ; end

Object.send(:remove_const, :Foo)

class Foo < String ; end # different Foo, no error

This way, each class Foo creates a new and independent class instead of re-opening the previous one.

Object is used for top-level constants. For nested constants, replace Object with the module that actually holds the constant.