This component provides a mechanism implementing the bmqa::AbstractSession protocol, for mocking a bmqa::Session and can be used to write a test for an application that uses BMQ. The bmqa::MockSession provides all the methods that bmqa::Session provides, with added methods to specify return codes and emitted events and expected calls. This can be used to test BlazingMQ application code without a connection to the broker. bmqa::MockSessionUtil is a utility namespace providing useful methods to build bmqa::Event objects that are typically only emitted from the broker.
The following documentation elucidates the API that this component provides and some simple use cases to get you started.
Disclaimer
- Warning
- THIS COMPONENT SHOULD ONLY BE USED IN TEST DRIVERS. IT WILL NOT WORK WITH PRODUCTION CODE.
Usable Components
- BMQA_EXPECT_CALL: Macro to specify an expected call to a bmqa::MockSession object. This macro is used to specify which is the next expected call on the bmqa::MockSession. If an incorrect call is invoked or incorrect parameters are used, an assert will be invoked.
- returning: Specify a return value for the expected call. This is the value that will be returned when the method on bmqa::MockSession is invoked.
- emitting: Specify an event to be
emitted
when the expected call is invoked. The events specified are enqueued to the internal event queue and are delivered to the application when emitEvent
is invoked.
Static Helper Methods
- createAckEvent: Create an acknowledgment message event for messages posted to BMQ.
- createPushEvent: Create a push message event for messages to be consumed from BMQ.
- createOpenQueueStatus: Create an openQueue result (relating to an async open queue operation)
- createConfigureQueueStatus: Create a configureQueue result (relating to an async configure queue operation)
- createCloseQueueStatus: Create a closeQueue result (relating to an async close queue operation)
- createSessionEvent: Create a specified type of session event except for events related to open, close and configure queue.
The static event builder specified above are typically built inside the broker but are now available to be built in the SDK. The expected use of such events is to build them and specify them to either the BMQA_EXPECT_CALL macro in the emitting
parameter, or enqueued to the bmqa::MockSession directly through the enqueueEvent
method. They can then be emitted by invoking the emitEvent
method, which in turn would be processed through the application-provided bmqa::SessionEventHandler.
Additional Note
bmqa::MockSession does not check if methods have been invoked in the correct order. The user is responsible for ensuring that the methods are invoked and events enqueued in the correct order.
The following methods do not emit events:
getQueueId
loadMessageEventBuilder
loadConfirmEventBuilder
loadMessageProperties
confirmMessage
confirmMessages
Calls to the following methods do not require an expect:
getQueueId
loadMessageEventBuilder
loadConfirmEventBuilder
loadMessageProperties
Creating a mock session in asynchronous mode
The bmqa::MockSession is created in asynchronous mode when a bmqa::SessionEventHandler is provided to it. If it is not provided a handler, the bmqa::MockSession is started in synchronous mode, requiring the application to call nextEvent
to access enqueued events. A sample handler could look like this:
class MyEventHandler : public bmqa::SessionEventHandler {
private:
bsl::deque<bmqa::SessionEvent> d_sessionEventsQueue;
bsl::deque<bmqa::MessageEvents> d_messageEventsQueue;
bsl::deque<bmqa::OpenQueueStatus> d_openQueueResultsQueue;
...
public:
virtual void onSessionEvent(const bmqa::SessionEvent& event)
{
bsl::cout << "Received session event " << event << "\n";
d_sessionEventsQueue.push_back(event);
}
virtual void onMessageEvent(const bmqa::MessageEvent& event)
{
bsl::cout << "Received message event " << event << "\n";
d_messageEventsQueue.push_back(event);
}
void onOpenQueueStatus(const bmqa::OpenQueueStatus& result)
{
bsl::cout << "Received open queue result: " << result << "\n";
d_openQueueResultsQueue.push_back(result);
}
...
bmqa::SessionEvent popSessionEvent()
{
BSLS_ASSERT(d_sessionEventsQueue.size() > 0);
bmqa::SessionEvent ret(d_receivedSessionEvents.front());
d_receivedSessionEvents.pop_front();
return ret;
}
bmqa::MessageEvent popMessageEvent()
{
BSLS_ASSERT(d_messageEventsSize.size() > 0);
bmqa::MessageEvent ret(d_receivedMessageEvents.front());
d_receivedMessageEvents.erase(d_receivedMessageEvents.begin());
return ret;
}
bmqa::OpenQueueStatus popOpenQueueStatus()
{
BSLS_ASSERT(d_openQueueResultsQueue.size() > 0);
bmqa::OpenQueueStatus ret(d_openQueueResultsQueue.front());
d_openQueueResultsQueue.erase(d_openQueueResultsQueue.begin());
return ret;
}
...
};
Usage
This section illustrates intended use of this component.
Example 1
The folowing example shows a simple producer in asynchronous mode, which will start the session, open a queue, post a message to the queue, generate an ack for that message and finally stop the session (skipping over close queue because it is analogous to opening a queue). In theory, you can use emitting
on the BMQA_EXPECT_CALL macro and enqueueEvent
interchangeably, but in practice it is important to note that events from the broker are generated asynchronously, which means that they are not emitted as you call the method. You can control emission of events, however, by delaying the call to emitEvent
.
- Note
- As with bmqa::Session, calling
nextEvent
is meaningless in asynchronous mode.
void unitTest()
{
EventHandler eventHandler(d_allocator_p);
bslma::ManagedPtr<bmqa::SessionEventHandler> handlerMp;
handlerMp.load(&eventHandler, 0, bslma::ManagedPtrUtil::noOpDeleter);
bmqa::MockSession mockSession(handlerMp,
bmqt::SessionOptions(d_allocator_p),
d_allocator_p);
bmqa::QueueId queueId(bmqt::CorrelationId(1), d_allocator_p);
bmqt::CorrelationId corrId(1);
.returning(0)
.emitting(bmqa::MockSessionUtil::createSessionEvent(
bmqt::SessionEventType::e_CONNECTED,
0,
"",
d_allocator_p));
ASSERT_EQ(mockSession.startAsync(), 0);
ASSERT_EQ(mockSession.emitEvent(), true);
bmqa::SessionEvent startEvent(eventHandler.popSessionEvent());
ASSERT_EQ(startEvent.type(), bmqt::SessionEventType::e_CONNECTED);
ASSERT_EQ(startEvent.status Code(), 0);
const bmqt::Uri uri("bmq://my.domain/queue");
bsls::Types::Uint64 flags = 0;
bmqt::QueueFlagsUtil::setWriter(&flags);
bmqt::QueueFlagsUtil::setAck(&flags);
bmqa::MockSession::OpenQueueCallback openQueueCallback =
bdlf::BindUtil::bind(&EventHandler::onOpenQueueStatus,
&eventHandler,
bdlf::PlaceHolders::_1);
openQueueAsync(uri1,
flags,
openQueueCallback));
openQueueAsync(uri, flags, openQueueCallback));
mockSession.openQueueAsync(uri1, flags, openQueueCallback);
bmqa::QueueId queueId1(corrId1);
bmqa::OpenQueueStatus openQueueResult =
bmqa::MockSessionUtil::createOpenQueueStatus(
queueId1,
bmqt::OpenQueueResult::e_TIMEOUT,
"Local Timeout",
d_allocator_p);
mockSession.enqueueEvent(openQueueResult);
ASSERT_EQ(mockSession.emitEvent(), true);
bmqa::OpenQueueStatus result = eventHandler.popOpenQueueStatus();
ASSERT_EQ(result, openQueueResult);
bmqa::MessageEventBuilder builder;
mockSession.loadMessageEventBuilder(&builder);
.returning(0);
ASSERT_EQ(mockSession.post(builder.messageEvent()), 0);
bdlbb::PooledBlobBufferFactory bufferFactory(4 * 1024, d_allocator_p);
bsl::vector<bmqa::MockSessionUtil::AckParams> acks(d_allocator_p);
acks.emplace_back(bmqt::AckResult::e_SUCCESS,
bmqt::CorrelationId(1),
bmqt::MessageGUID(),
bmqa::QueueId(1));
mockSession.enqueueEvent(bmqa::MockSessionUtil::createAckEvent(
acks,
&bufferFactory,
d_allocator_p));
ASSERT_EQ(mockSession.emitEvent(), true);
bmqa::MessageEvent ackEvent(eventHandler.popMessageEvent());
ASSERT_EQ(ackEvent.type(), bmqt::MessageEventType::e_ACK);
bmqa::MessageIterator mIter = ackEvent.messageIterator();
mIter.nextMessage();
ASSERT_EQ(mIter.message().ackStatus(), bmqt::AckResult::e_SUCCESS);
mockSession.stopAsync();
mockSession.enqueueEvent(bmqa::MockSessionUtil::createSessionEvent(
bmqt::SessionEventType::e_DISCONNECTED,
0,
"",
d_allocator_p));
ASSERT_EQ(mockSession.emitEvent(), true);
bmqa::SessionEvent stopEvent(eventHandler.popSessionEvent());
ASSERT_EQ(stopEvent.type(), bmqt::SessionEventType::e_DISCONNECTED);
ASSERT_EQ(stopEvent.statusCode(), 0);
}
#define BMQA_EXPECT_CALL(OBJ, CALL)
Definition: bmqa_mocksession.h:598
Example 2
The folowing example shows a consumer in synchronous mode, which will start the session, generate a push message (simulating the broker), confirm the message and then stop the session. Additionally, this test case also sets all expectations up front before running the code, as this is the alternate way of writing your test driver.
- Note
- Using
enqueue
or emitEvent
on bmqa::MockSession or emitting
on the BMQA_EXPECT_CALL macro in synchronous mode is meaningless.
void unitTest()
{
bmqa::MockSession mockSession(bmqt::SessionOptions(d_allocator_p),
d_allocator_p);
bmqa::QueueId queueId(1);
bmqt::CorrelationId corrId(1);
bmqt::Uri uri("bmq://my.domain/queue");
.returning(0);
bdlbb::PooledBlobBufferFactory bufferFactory(4 * 1024, d_allocator_p);
.returning(bmqa::MockSessionUtil::createSessionEvent(
bmqt::SessionEventType::e_CONNECTED,
bmqt::CorrelationId::autoValue(),
0,
"",
d_allocator_p));
bsls::Types::Uint64 flags = 0;
bmqt::QueueFlagsUtil::setReader(&flags);
bmqa::OpenQueueStatus expectedResult =
bmqa::MockSessionUtil::createOpenQueueStatus(
queueId,
bmqt::OpenQueueResult::e_SUCCESS,
"",
d_allocator_p);
.returning(expectedResult);
bsl::vector<bmqa::MockSessionUtil::PushMessageParams> pushMsgs(
d_allocator_p);
bdlbb::Blob payload(&bufferFactory, d_allocator_p);
bdlbb::BlobUtil::append(&payload, "hello", 6);
const char guidHex[] = "00000000000000000000000000000001";
bmqt::MessageGUID guid;
guid.fromHex(guidHex);
bmqa::MessageProperties properties;
mockSession.loadMessageProperties(&properties);
pushMsgs.emplace_back(payload, queueId, guid, properties);
bmqa::Event pushMsgEvent = bmqa::MockSessionUtil::createPushEvent(
pushMsgs,
&bufferFactory,
d_allocator_p);
.returning(pushMsgEvent);
bmqa::ConfirmEventBuilder confirmBuilder;
mockSession.loadConfirmEventBuilder(&confirmBuilder);
.returning(0);
int rc = mockSession.startAsync();
ASSERT_EQ(rc, 0);
bmqa::SessionEvent startEvent = mockSession.nextEvent(
bsls::TimeInterval())
.sessionEvent();
ASSERT_EQ(startEvent.type(), bmqt::SessionEventType::e_CONNECTED);
ASSERT_EQ(startEvent.statusCode(), 0);
ASSERT_EQ(startEvent.errorDescription(), "");
bmqa::OpenQueueStatus result = mockSession.openQueueSync(&queueId,
uri,
flags);
ASSERT_EQ(result, expectedResult);
bmqa::MessageEvent pushMsgEvt(mockSession.nextEvent(
bsls::TimeInterval())
.messageEvent());
ASSERT_EQ(pushMsgEvt.type(), bmqt::MessageEventType::e_PUSH);
ASSERT_EQ(mockSession.unconfirmedMessages(), 1U);
bmqa::MessageIterator mIter = pushMsgEvt.messageIterator();
mIter.nextMessage();
confirmBuilder.addMessageConfirmation(mIter.message());
ASSERT_EQ(confirmBuilder.messageCount(), 1);
rc = mockSession.confirmMessages(&confirmBuilder);
ASSERT_EQ(rc, 0);
ASSERT_EQ(mockSession.unconfirmedMessages(), 0u);
}
Thread Safety
THREAD SAFE.