RSpec & Redis

Redis ist ein Eimer, eine Deponie oder einfach ein Key/ Value Speicher. Es ist sehr praktisch zum Cachen.
Beim Testen der darüber liegenden Business Logik sollten einige Dinge beachtet werden.

Redis Business Logik

Zunächst soll ein einfacher Redis Wrapper als beispielhafte Implementierung einer Business Logik (basierend auf Redis) dienen.
Zunächst soll als beispielhafte Implementierung einer Business Logik, basierend auf Redis, ein einfacher Redis Wrapper dienen.
Ziel dieses Wrappers ist es, Objekte in Redis abzulegen, allerdings mit einem eindeutigen Key.
Ein Anwendungsfall könnten ActiveRecord Objekte sein, da sie anhand ihrer ID nicht eindeutig in Redis abgelegt werden können, da es mehrere unterschiedliche ActiveRecord Objekte mit der gleichen ID geben kann.
Dieser Wrapper bildet einen den eindeutigen Key aus dem Klassennamen und seiner ID:

module Model
  class Cache
    attr_reader :record

    def initialize(record)
      @record = record
    end

    def dump(value)
      redis.set key, value 
    end

    def find
      redis.get key
    end

    private

    def key
      "#{record.class}:#{record.id}"
    end

    def redis
      @redis ||= Redis.new
    end
  end
end

Er kann dann anhand des Keys, einen Wert in Redis ablegen und auch wiederfinden.
Es gibt ähnliche Ansätze in bereits bestehenden Ruby Gems.
Diese Implementierung soll mit RSpec getestet werden.

Redis Spec Helper

Zunächst macht es Sinn, einen Redis Spec Helper einzuführen, der sich um Aufgaben kümmert, die sich für jeden Test wiederholen und keine Nutzen für die Lesbarkeit der Spezifikation bringen. Ein minimaler redis_spec_helper.rb:

RSpec.configure do |config|
  config.before(:each) { redis.flushdb }
  config.after(:each) { redis.quit }
end

Er leert alle Redis Speicherungen vor jedem Test und beendet nach jedem Test die Verbindung zu Redis.
Es könnte auch das Setzen einer bestimmten Redis Datenbankinstanz beinhalten oder andere Test Helper.

Unit Test Vorbereitungen für Redis

Der Unit Test bedarf einiger Testfallvorbereitungen. Insbesondere das testbare Model::Cache Objekt als subject:

require 'rspec'
require 'redis_helper'

TestModel = Struct.new :id

RSpec.describe Model::Cache do
  let!(:redis) { Redis.new }
  let!(:model) { TestModel.new rand(1..10) }
  let!(:model_key) { "TestModel:#{model.id}" }

  subject { Model::Cache.new model }

  describe '#model' do
    it 'returns the model' do
      expect(subject.model).to be model
    end
  end
end

Außerdem wurden einige Variablen angelegt, nämlich: * redis (ein unabhängiger Redis Datenbankzugriff) * model (ein Struct Objekt mit einer zufälligen ID, dass das Model darstellt) * model_key (der erwartete Model Key) Schließlich wird schon einmal der model accessor model getestet.

Unit tests mit Redis

Die Tests für die beiden Methoden dump und find decken dann das erwartete Verhalten ab:

require 'rspec'
require 'redis_helper'

TestModel = Struct.new :id

RSpec.describe Model::Cache do
  let!(:redis) { Redis.new }
  let!(:model) { TestModel.new rand(1..10) }
  let!(:model_key) { "TestModel:#{model.id}" }

  subject { Model::Cache.new model }

  describe '#model' do
    it 'returns the model' do
      expect(subject.model).to be model
    end
  end

  describe '#dump' do
    before { subject.dump 'test' }

    it 'dumps the models key into Redis' do
      expect(redis.keys).to eq([model_key])
    end

    it 'dumps the value for the models key into Redis' do
      expect(redis.get(model_key)).to eq('test')
    end
  end

  describe '#find' do
    it 'finds the records value' do
      redis.set model_key, 'test'
      expect(subject.find).to eq('test')
    end

    context 'when dumps an Integer' do
      it 'returns the value as a String' do
        redis.set model_key, 123
        expect(subject.find).to eq('123')
      end
    end
  end
end