Testing consistency guarantees

We are writing integrations tests to confirm that we are getting the expected consistency guarantees when using NServiceBus and so verify that we have not misconfigured it.

We are using SqlServer transport, with TransportTransactionMode.SendsAtomicWithReceive.
When inside a message handler we take the NServiceBus message handling transaction and use if in our DbContext when updating domain entities.

We have a test scenario that sends a command, and in the command handler we modify a domain entity and publish NServiceBus events.
Our tests throw exceptions (or don’t) at various points and assert that everything either:

  • entity is not modified, events are not published, and the original command is retried, or
  • entity is modfified, events are published, and the original command is not retried.

The tests all pass, but if we don’t share the NSB transaction with our DbContext, then we can have entities modified even when the handler fails.
This is all what we want and what we expect.

However, I have noticed that if I change the transport mode to TransportTransactionMode.ReceiveOnly, our tests all pass.
At first, I would have expected that I events might still successfully be published even if the handler fails at the end.
From the docs, I am inferring that we are not seeing test failures because the publishing is batched and so doesn’t occur until the handler succeeds.
Question 1: Is that inference correct?

To verify that, and write a test that would fail when using TransportTransactionMode.ReceiveOnly I expect I would need to somehow force an exception after the handler (whole pipeline) has successfully completed, but before batched messages are published. This seems hard to do without modifying NServiceBus code, or manual testing with breakpoints in NServiceBus code where I could kill it.
Question 2: Is there some other way to manifest an error in this way, so I can verify my test would catch it?

Question 3: If you are using SqlServer transport, and sharing the NSB-owned transaction with your DbContext is there any practical difference between TransportTransactionMode.TransactionScope , TransportTransactionMode.SendsAtomicWithReceive, and TransportTransactionMode.ReceiveOnly (given we are not using sagas)?
I am expecting that there are not practical differences between the first two, but maybe the batched publish doesn’t use the same transaction if you are using TransportTransactionMode.ReceiveOnly?

Hi @gb-8

Let me try to answer your questions.

From the docs, I am inferring that we are not seeing test failures because the publishing is batched and so doesn’t occur until the handler succeeds.
Question 1 : Is that inference correct?

Yes, the outgoing messages are batched by default. In order to force messages to be sent out immediately you can request immediate dispatch.

To verify that, and write a test that would fail when using TransportTransactionMode.ReceiveOnly I expect I would need to somehow force an exception after the handler (whole pipeline) has successfully completed, but before batched messages are published. This seems hard to do without modifying NServiceBus code, or manual testing with breakpoints in NServiceBus code where I could kill it.
Question 2 : Is there some other way to manifest an error in this way, so I can verify my test would catch it?

Actually, in order to verify that you need to trigger the exception after the batched messages are sent out. If you trigger it before, the receive transaction will be rolled back and you will see no side effects.

You can create a behavior in the ITransportReceiveContext that first executes the rest of the pipeline and then throw an exception. This will ensure that the TransportReceiveToPhysicalMessageConnector dispatches the outgoing messages before you roll back the receive transaction (along with rolling back your data changes).

Question 3 : If you are using SqlServer transport, and sharing the NSB-owned transaction with your DbContext is there any practical difference between TransportTransactionMode.TransactionScope , TransportTransactionMode.SendsAtomicWithReceive , and TransportTransactionMode.ReceiveOnly (given we are not using sagas)?
I am expecting that there are not practical differences between the first two, but maybe the batched publish doesn’t use the same transaction if you are using TransportTransactionMode.ReceiveOnly ?

You are right, the ReceiveOnly mode means that the dispatching is not part of the receive transaction.

Regarding TransactionScope and SendsAtomicWithReceive there is little practical difference. In order to have your business logic atomic with message consumption in the former you need to open a new SqlConnection inside the scope managed by the transport and in the latter you need to get the transport-managed SqlConnection. On the wire the behavior is exactly the same as ADO.NET driver ensure that in the TransactionScope mode both instances of the SqlConnection class use the same underlying connection to the database.