Populate `EnvelopeRecipientCollection` on `Recipients` Property of a `MailItem` in Transport Agent
I've written an Exchange Transport Agent in C#, using the common assemblies from Exchange 2019 CU14 (namely:
Microsoft.Exchange.Data.Common.dll
and Microsoft.Exchange.Data.Transport.dll
).
I, at first, had issues populating the MailItem
object on the EndOfDataEventArgs
delegate. I discovered that I could do this via reflection and now I'm trying to test a condition that the message is quarantined. In order for the message to be quarantined, the recipients - which is a List<EnvelopeRecipient>
has to be populated and passed into the ReceiveMessageEventSource.Quarantine
method.
The issue that I'm running into is that the Recipients
property on the MailItem
only has a getter and is of type EnvelopeRecipientCollection
. So, I try to setup a Mock<EnvelopeRecipientCollection>
and am able to feed that as the SetupGet(x => x.Recipients).Returns(...)
; however, because it's a mock, it's an empty collection and the test is failing because the recipient collection is empty.
Since the constructor for EnvelopeRecipientCollection
is internal, I can't instantiate an instance of the object.
It looks like EnvelopeRecipientCollection
depends on a struct of IEnumerator<EnvelopeRecipient>
, so trying to pass the List<EnvelopeRecipient>.GetEnumerator()
as the EnvelopeRecipientCollection's enumerator doesn't work, either.
Is there a way to successfully "fake" an object - even if it's a List<EnvelopeRecipient>
- as an EnvelopeRecipientCollection
and pass that into the SetupGet
, so that the test doesn't continually fail with a NullReferenceException
when trying to fetch the recipients for the ReceiveMessageEventSource.Quarantine
method?
Recipients Property (for 2010 - but hasn't changed in 2019): https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa579340(v=exchg.140)
EnvelopeRecipientCollection: https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa564107(v=exchg.140)11 Replies
Any chance you got a code snippet making the issue a bit clearer?
When you say some of these objects only have private setters etc, can you backtrack to where the objects are instantiated? They'd probably receive that part as a constructor parameter I assume
Source message comes from here: https://techcommunity.microsoft.com/blog/exchange/how-to-write-an-exchange-2013-transport-agent/596860
The helper to translate the email to an email item also came from there.
This is the deconstructed view of the MailItem. As you can see, it only has a
get
for the Recipients
property and the constructor is marked as internal
. When Recipients
is populated, I have to assume it's on the EndOfHeaders process in the Transport Pipeline, itself?And here's the deconstructed view of EnvelopeRecipientCollection.
Sorry, forgot: SetPropertyValue is just a reflection extension.
`
might be enough with
mockEnvelopeRecipientCollection.Setup(x => x.GetEnumerator()).Returns(recipients.GetEnumerator());
if all you depend on is IEnumerable<EnvelopeRecipient>
Gives the error about being different types:
Argument 1: cannot convert from 'System.Collections.Generic.List<Microsoft.Exchange.Data.Transport.EnvelopeRecipient>.Enumerator' to 'Microsoft.Exchange.Data.Transport.EnvelopeRecipientCollection.Enumerator'
Is there a reason you are trying to work with classes that are from an external library? I feel like you should not be unit testing those
I'm not testing classes from the external libraries but my own logic in the
EndOfDataEventHandler
delegate. Part of that logic is quarantining a message via ReceiveMessageEventSource.Quarantine
, which requires an IEnumerable<EnvelopeRecipient>
to be passed in. The only way I can derive that list is from the EnvelopeRecipientCollection
from the MailItem
, I believe.
https://learn.microsoft.com/en-us/previous-versions/office/exchange-server-api/aa567154(v=exchg.150)
https://learn.microsoft.com/en-us/previous-versions/office/exchange-server-api/aa564601(v=exchg.150)I would build a layer between this external API and your business logic. That way you can test your business logic without needing to construct external models