How to undefine class in Ruby?

后端 未结 2 579
春和景丽
春和景丽 2020-12-03 02:48

Undefining a method in Ruby is pretty simple, I can just use undef METHOD_NAME.

Is there anything similar for a class? I am on MRI 1.9.2. <

相关标签:
2条回答
  • 2020-12-03 03:12

    In a similar situation - mocking a class used internally by another class I'm trying to test - I found this to be a workable solution:

    describe TilesAuth::Communicator do
      class FakeTCPSocket
        def initialize(*_); end
        def puts(*_); end
      end
    
      context "when the response is SUCCESS" do
        before do
          class TilesAuth::Communicator::TCPSocket < FakeTCPSocket
            def gets; 'SUCCESS'; end
          end
        end
        after { TilesAuth::Communicator.send :remove_const, :TCPSocket }
    
        it "returns success" do
          communicator = TilesAuth::Communicator.new host: nil, port: nil, timeout: 0.2
          response = communicator.call({})
          expect(response["success"]).to eq(true)
          expect(response).not_to have_key("error")
          expect(response).not_to have_key("invalid_response")
        end
      end
    end
    

    I would have thought there would be a better way to do this - i.e. I couldn't see a way to pass in the desired return value for reuse - but this seems good enough for now. I'm new to mocking/factories, and I'd love to hear about any alternative methods.

    Edit:

    Ok, so not so similar after all.

    I found a better way using RSpec mock, thanks to an excellent explanation in the RSpec Google Group:

    context "with socket response mocked" do
      let(:response) do
        tcp_socket_object = instance_double("TCPSocket", puts: nil, gets: socket_response)
        class_double("TCPSocket", new: tcp_socket_object).as_stubbed_const
        communicator = TilesAuth::Communicator.new host: nil, port: nil, timeout: 0.2
        communicator.call({})
      end
    
      context "as invalid JSON" do
        let(:socket_response) { 'test invalid json' }
    
        it "returns an error response including the invalid socket response" do
          expect(response["success"]).to eq(false)
          expect(response).to have_key("error")
          expect(response["invalid_response"]).to eq(socket_response)
        end
      end
    
      context "as SUCCESS" do
        let(:socket_response) { 'SUCCESS' }
    
        it "returns success" do
          expect(response["success"]).to eq(true)
          expect(response).not_to have_key("error")
          expect(response).not_to have_key("invalid_response")
        end
      end
    end
    
    0 讨论(0)
  • 2020-12-03 03:14
    class Foo; end
    # => nil
    Object.constants.include?(:Foo)
    # => true
    Object.send(:remove_const, :Foo)
    # => Foo
    Object.constants.include?(:Foo)
    # => false
    Foo
    # NameError: uninitialized constant Foo
    

    EDIT Just noticed your edit, removing the constant is probably not the best way to achieve what you're looking for. Why not just move one of the Contact classes into a separate namespace.

    EDIT2 You could also rename your class temporarily like this:

    class Foo
      def bar
        'here'
      end
    end
    
    TemporaryFoo = Foo
    Object.send(:remove_const, :Foo)
    # do some stuff
    Foo = TemporaryFoo
    Foo.new.bar #=> "here"
    

    Again, the trouble with this is that you'll still have the newer Contact class so you'll have to remove that again. I would really recommend name-spacing your classes instead. This will also help you avoid any loading issues

    0 讨论(0)
提交回复
热议问题