Testing Adapters
Shared RSpec contracts and testing strategies for custom adapters and state backends.
ChatSDK provides shared RSpec examples (contracts) to verify that custom adapters and state backends conform to the expected interface.
Adapter Contract
The adapter contract verifies that your adapter:
- Returns a
Symbolfrom#name - Responds to
#client - Responds to
#verify_request!,#parse_events,#post_message,#mention,#render,#supports? - Raises
NotSupportedErrorfor undeclared capabilities
require "chat_sdk/testing/adapter_contract"
RSpec.describe MyAdapter do
subject { MyAdapter.new(api_key: "test-key") }
it_behaves_like "a chat_sdk platform adapter"
endState Contract
The state contract verifies that your state backend correctly implements:
- Subscriptions: subscribe, unsubscribe, subscribed?
- Locks: acquire_lock, release_lock, force_lock (including owner-based release)
- Key-Value: get, set, delete, set_if_absent
require "chat_sdk/testing/state_contract"
RSpec.describe MyState do
subject { MyState.new }
it_behaves_like "a chat_sdk state adapter"
endUnit Testing Event Parsing
Test that your adapter correctly parses platform payloads into ChatSDK events:
RSpec.describe MyAdapter do
let(:adapter) { MyAdapter.new(api_key: "test") }
describe "#parse_events" do
it "parses a message event" do
request = mock_rack_request(
body: '{"type":"message","text":"hello","user_id":"U1","channel_id":"C1"}',
content_type: "application/json"
)
events = adapter.parse_events(request)
expect(events.size).to eq(1)
expect(events.first).to be_a(ChatSDK::Events::Mention)
expect(events.first.message.text).to eq("hello")
end
end
endIntegration Testing with FakeAdapter
Test your bot logic using FakeAdapter without needing your real adapter:
RSpec.describe "MyBot" do
let(:bot) { ChatSDK::Testing.build_bot }
let(:adapter) { bot.config.adapters[:test] }
before { adapter.reset! }
it "responds to mentions" do
bot.on_new_mention do |thread, message|
thread.post("Echo: #{message.text}")
end
adapter.simulate_mention(bot, text: "hello")
expect(adapter.posted_messages.size).to eq(1)
expect(adapter.posted_messages.first.message.text).to eq("Echo: hello")
end
it "handles actions" do
bot.on_action("my_btn") do |event|
event.thread.post("Clicked!")
end
adapter.simulate_action(bot, action_id: "my_btn")
expect(adapter.posted_messages.size).to eq(1)
end
endTesting Card Rendering
Verify that your card renderer produces valid platform output:
RSpec.describe MyCardRenderer do
let(:renderer) { MyCardRenderer.new }
it "renders a text node" do
card = ChatSDK.card { text "Hello" }
result = renderer.render(card)
expect(result).to include("Hello")
end
it "renders buttons" do
card = ChatSDK.card do
actions do
button "Click", id: "btn_1", value: "val"
end
end
result = renderer.render(card)
expect(result).to include("btn_1")
end
endTesting Signature Verification
RSpec.describe MyAdapter do
let(:adapter) { MyAdapter.new(api_key: "test", signing_secret: "secret") }
it "rejects invalid signatures" do
request = mock_rack_request(
body: "payload",
headers: { "HTTP_X_SIGNATURE" => "invalid" }
)
expect { adapter.verify_request!(request) }
.to raise_error(ChatSDK::SignatureVerificationError)
end
it "accepts valid signatures" do
request = mock_rack_request(
body: "payload",
headers: { "HTTP_X_SIGNATURE" => compute_signature("payload", "secret") }
)
expect { adapter.verify_request!(request) }.not_to raise_error
end
endWebMock for API Calls
Use WebMock to stub outbound API calls in adapter tests:
require "webmock/rspec"
RSpec.describe MyAdapter do
let(:adapter) { MyAdapter.new(api_key: "test") }
it "posts a message" do
stub_request(:post, "https://api.myplatform.com/messages")
.to_return(body: '{"id":"msg_1"}', status: 200)
result = adapter.post_message(
channel_id: "C1",
message: ChatSDK::PostableMessage.new(text: "Hello")
)
expect(result.id).to eq("msg_1")
end
end