Quick Links: |
Provide a mock session, implementing bmqa::AbstractSession
.
More...
Namespaces | |
namespace | bmqimp |
namespace | bmqa |
bmqa::AbstractSession
. bmqa::MockSession | mechanism to mock a bmqa::Session |
bmqa::MockSessionUtil | utility methods to create bmqa events |
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 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. 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. bmqa::MockSession
is invoked. 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. 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
. 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. getQueueId
loadMessageEventBuilder
loadConfirmEventBuilder
loadMessageProperties
confirmMessage
confirmMessages
getQueueId
loadMessageEventBuilder
loadConfirmEventBuilder
loadMessageProperties
MockSession
is created in asynchronous mode when a SessionEventHandler
is provided to it. If it is not provided a handler, the 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: // DATA bsl::deque<bmqa::SessionEvent> d_sessionEventsQueue; bsl::deque<bmqa::MessageEvents> d_messageEventsQueue; bsl::deque<bmqa::OpenQueueStatus> d_openQueueResultsQueue; ... public: // MANIPULATORS virtual void onSessionEvent(const bmqa::SessionEvent& event) { bsl::cout << "Received session event " << event << "\n"; // some business logic, typically a switch case on // 'bmqt::SessionEventType' d_sessionEventsQueue.push_back(event); } virtual void onMessageEvent(const bmqa::MessageEvent& event) { bsl::cout << "Received message event " << event << "\n"; // some business logic, typically a switch case on // 'bmqt::MessageEventType' d_messageEventsQueue.push_back(event); } void onOpenQueueStatus(const bmqa::OpenQueueStatus& result) { bsl::cout << "Received open queue result: " << result << "\n"; // Some business logic 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; } ... };
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
. bmqa::Session
, calling nextEvent
is meaningless in asynchronous mode. void unitTest() { // Create an event handler EventHandler eventHandler(d_allocator_p); // The following static initializer method calls all the appropriate // static initializers of the underlying components needed for the // 'MockSession'. The constructor of 'MockSession' will call it in // any case but if events need to be built outside the scope of the // creation of 'MockSession' you will need to explicitly invoke this // static initializer method. // bmqa::MockSession::initialize(s_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); // Expect a call to start and the call emits an 'e_CONNECTED' event. BMQA_EXPECT_CALL(mockSession, startAsync()) .returning(0) .emitting(bmqa::MockSessionUtil::createSessionEvent( bmqt::SessionEventType::e_CONNECTED, 0, // statusCode "", // errorDescription d_allocator_p)); // Make a call to startAsync and emit the event that is enqueued from // that call. ASSERT_EQ(mockSession.startAsync(), 0); // Emit our enqueued event. This fully sets up the session which is // now ready to use. Typically you would have some business logic on // 'e_CONNECTED' that makes your application ready to use. ASSERT_EQ(mockSession.emitEvent(), true); // Our event handler internally just stores the event emitted, so pop // it out and examine. bmqa::SessionEvent startEvent(eventHandler.popSessionEvent()); ASSERT_EQ(startEvent.type(), bmqt::SessionEventType::e_CONNECTED); ASSERT_EQ(startEvent.statusCode(), 0); // Create the uri to your queue as you would in your application. const bmqt::Uri uri("bmq://my.domain/queue"); // Initialize the queue flags for a producer with acks enabled bsls::Types::Uint64 flags = 0; bmqt::QueueFlagsUtil::setWriter(&flags); bmqt::QueueFlagsUtil::setAck(&flags); // We use the macro to expect a call to 'openQueueAsync', binding the // 'uri' and 'queueId' objects as well as the 'flags' that we created. bmqa::MockSession::OpenQueueCallback openQueueCallback = bdlf::BindUtil::bind(&EventHandler::onOpenQueueStatus, &eventHandler, bdlf::PlaceHolders::_1); // result BMQA_EXPECT_CALL(mockSession, openQueueAsync(uri1, flags, openQueueCallback)); BMQA_EXPECT_CALL(mockSession, openQueueAsync(uri, flags, openQueueCallback)); // Now that we have set our expectations we can try to open the queue. mockSession.openQueueAsync(uri1, flags, openQueueCallback); // Since the application may not have direct access to the queue, we // need to get the 'queueId' from the session. We can then bind this // retrieved 'queueId' to the 'e_QUEUE_OPEN_RESULT' session event and // enqueue it to the 'MockSession'. // Note: You can only get the 'queueId' after 'openQueue' or // 'openQueueAsync' has been invoked on the session. bmqa::QueueId queueId1(corrId1); bmqa::OpenQueueStatus openQueueResult = bmqa::MockSessionUtil::createOpenQueueStatus( queueId1, bmqt::OpenQueueResult::e_TIMEOUT, // statusCode "Local Timeout", // errorDescription d_allocator_p); mockSession.enqueueEvent(openQueueResult); // We just enqueued a 'bmqa::OpenQueueStatus' to be emitted. We can // emit it using 'emitEvent'. ASSERT_EQ(mockSession.emitEvent(), true); // Pop out this event from the handler and examine it. bmqa::OpenQueueStatus result = eventHandler.popOpenQueueStatus(); ASSERT_EQ(result, openQueueResult); // On emission of 'bmqa::OpenQueueStatus', the queue is fully open and // we can now post to it. bmqa::MessageEventBuilder builder; mockSession.loadMessageEventBuilder(&builder); BMQA_EXPECT_CALL(mockSession, post(builder.messageEvent())) .returning(0); // Use the builder to build a mesage event and pack it for the queue // that has been opened. If you try to pack the message for an // invalid or closed queue, packing the message will fail. This has // been elided for brevity. // Now that the event has been built we can 'post' it to BMQ. ASSERT_EQ(mockSession.post(builder.messageEvent()), 0); // Simply creating a blob buffer factory on the stack to be used by // 'createAckEvent'. Typically you would have one for the component. bdlbb::PooledBlobBufferFactory bufferFactory(4 * 1024, d_allocator_p); // The method 'createAckEvent' takes a vector of 'AckParams' to // specify multiple acks per event, but here we are only acknowledging // 1 message. Specify a positive ack with 'e_SUCCESS' here but you // can specify any from 'bmqt::AckResult::Enum'. bsl::vector<bmqa::MockSessionUtil::AckParams> acks(d_allocator_p); acks.emplace_back(bmqt::AckResult::e_SUCCESS, bmqt::CorrelationId(1), bmqt::MessageGUID(), // Real GUID needed if you want // to record ack messages. bmqa::QueueId(1)); // Enqueuing ack event to be emitted. We use the helper function // 'createAckEvent' to generate this event. mockSession.enqueueEvent(bmqa::MockSessionUtil::createAckEvent( acks, &bufferFactory, d_allocator_p)); // Emit the enqueued ack event. ASSERT_EQ(mockSession.emitEvent(), true); // As we did earlier, pop it out and examine. 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); // This is a simple test. After posting our message and receiving the // ack, we are now shutting down our application. Therefore we expect // a 'stopAsync' call. BMQA_EXPECT_CALL(mockSession, stopAsync()); // Now make a call to 'stopAsync' to stop our session. mockSession.stopAsync(); // Here we are enqueuing an 'e_DISCONNECTED' event as you would // receive from the broker on a successful shutdown. mockSession.enqueueEvent(bmqa::MockSessionUtil::createSessionEvent( bmqt::SessionEventType::e_DISCONNECTED, 0, // statusCode "", // errorDescription d_allocator_p)); ASSERT_EQ(mockSession.emitEvent(), true); // Our event handler internally just stores the event emitted, so pop // it out and examine. bmqa::SessionEvent stopEvent(eventHandler.popSessionEvent()); ASSERT_EQ(stopEvent.type(), bmqt::SessionEventType::e_DISCONNECTED); ASSERT_EQ(stopEvent.statusCode(), 0); // The corresponding pendant operation of the 'initialize' which would // need to be called only if 'initialize' was explicitly called. // bmqa::MockSession::shutdown(); }
enqueue
or emitEvent
on bmqa::MockSession
or emitting
on the BMQA_EXPECT_CALL
macro in synchronous mode is meaningless. void unitTest() { // MockSession created without an eventHandler. bmqa::MockSession mockSession(bmqt::SessionOptions(d_allocator_p), d_allocator_p); // The following static initializer method calls all the appropriate // static initializers of the underlying components needed for the // 'MockSession'. The constructor of 'MockSession' will call it in // any case but if events need to be built outside the scope of the // creation of 'MockSession' you will need to explicitly invoke this // static initializer method. // bmqa::MockSession::initialize(s_allocator_p); // Create simple queueIds and corrIds bmqa::QueueId queueId(1); bmqt::CorrelationId corrId(1); // Create the uri to your queue as you would in your application. bmqt::Uri uri("bmq://my.domain/queue"); // Expecting that 'startAsync' will be called on the MockSession. BMQA_EXPECT_CALL(mockSession, startAsync()) .returning(0); // Simply creating a blob buffer factory on the stack to be used by // 'createAckEvent'. Typically you would have one for the component. bdlbb::PooledBlobBufferFactory bufferFactory(4 * 1024, d_allocator_p); // We then expect that 'nextEvent' will be called to return the // 'e_CONNECTED' event from the broker BMQA_EXPECT_CALL(mockSession, nextEvent(bsls::TimeInterval())) .returning(bmqa::MockSessionUtil::createSessionEvent( bmqt::SessionEventType::e_CONNECTED, bmqt::CorrelationId::autoValue(), 0, // errorCode "", // errorDescription d_allocator_p)); // Note that we use an 'autoValue' for correlationId because it's // irrelevant for a 'CONNECTED' event. // Initialize the queue flags for a consumer bsls::Types::Uint64 flags = 0; bmqt::QueueFlagsUtil::setReader(&flags); // We use the macro to expect a call to 'openQueueSync', binding the // 'uri' and 'queueId' objects as well as the flags that we created. // Note that the 'queueId' object will be modified as 'openQueueSync' // takes it as an output parameter. bmqa::OpenQueueStatus expectedResult = bmqa::MockSessionUtil::createOpenQueueStatus( queueId, bmqt::OpenQueueResult::e_SUCCESS, // statusCode "", // errorDescription d_allocator_p); BMQA_EXPECT_CALL(mockSession, openQueueSync(&queueId, uri, flags)) .returning(expectedResult); // Build our incoming message event. 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); // For each message that we are supposed to receive from the broker, // we need to specify the payload, the queueId, a guid (the hex is // random but unique within your test driver) and properties which // could be empty. pushMsgs.emplace_back(payload, queueId, guid, properties); bmqa::Event pushMsgEvent = bmqa::MockSessionUtil::createPushEvent( pushMsgs, &bufferFactory, d_allocator_p); BMQA_EXPECT_CALL(mockSession, nextEvent(bsls::TimeInterval())) .returning(pushMsgEvent); // Next we expect a call to 'confirmMessages', to confirm the 1 message // that we received from the broker. bmqa::ConfirmEventBuilder confirmBuilder; mockSession.loadConfirmEventBuilder(&confirmBuilder); BMQA_EXPECT_CALL(mockSession, confirmMessages(&confirmBuilder)) .returning(0); // Expectations have been set up. Now we run the code. // 'startAsync' is the first call. We expect it to return 0 and we // expect 'nextEvent' to return the 'e_CONNECTED' session event. 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(), ""); // Next we expect a call to 'openQueue' to open the queue. bmqa::OpenQueueStatus result = mockSession.openQueueSync(&queueId, uri, 10); ASSERT_EQ(result, expectedResult); // Now our call to 'nextEvent' will generate a push message from the // broker, which we will then go on to confirm. bmqa::MessageEvent pushMsgEvt(mockSession.nextEvent( bsls::TimeInterval()) .messageEvent()); ASSERT_EQ(pushMsgEvt.type(), bmqt::MessageEventType::e_PUSH); // Now that we have received a push message which has yet to be // confirmed, we can confirm that 1 unconfirmed message exists. ASSERT_EQ(mockSession.unconfirmedMessages(), 1U); // Since there is only 1 message in our message event, we dont have to // iterate over the event but in reality you will want to iterate over // each message and add it to the confirm builder. bmqa::MessageIterator mIter = pushMsgEvt.messageIterator(); mIter.nextMessage(); confirmBuilder.addMessageConfirmation(mIter.message()); ASSERT_EQ(confirmBuilder.messageCount(), 1); // Confirm the messages using the builder that has been populated. rc = mockSession.confirmMessages(&confirmBuilder); ASSERT_EQ(rc, 0); // Voila! We now have no unconfirmed messages. ASSERT_EQ(mockSession.unconfirmedMessages(), 0u); // 'stop' has been elided for brevity and is analogous to 'start' // The corresponding pendant operation of the 'initialize' which would // need to be called only if 'initialize' was explicitly called. // bmqa::MockSession::shutdown(); }