如何用Moq.Mock或Rhino.Mocks模拟ObjOutlookApplication.Session.Stores编写单元测试
Got it, let's work through this unit testing challenge for your Outlook add-in. The main hurdle here is that Outlook’s native Application, Session, and Stores are COM objects—they aren’t built to be easily mocked directly. But with a bit of abstraction and a mocking framework like Moq, we can fully test your IsMyPstStoreIsAvailable method, including the foreach loop logic.
Step 1: Add Abstractions for Outlook Objects
First, we’ll create interfaces that mirror the parts of Outlook’s objects we need. This lets us mock them later without relying on the actual Outlook COM objects:
// Interface for a single Outlook Store public interface IOutlookStore { string StoreID { get; } } // Interface for Outlook's Session object public interface IOutlookSession { IEnumerable<IOutlookStore> Stores { get; } } // Interface for the main Outlook Application object public interface IOutlookApplication { IOutlookSession Session { get; } }
Step 2: Refactor Your Service Class to Use Dependencies
Next, we’ll adjust your original class to depend on these interfaces instead of directly using the Outlook COM objects. This follows the Dependency Inversion Principle and makes testing straightforward:
public class OutlookStoreChecker { private readonly IOutlookApplication _outlookApp; // Inject the abstracted Outlook application instead of using a concrete COM object public OutlookStoreChecker(IOutlookApplication outlookApp) { _outlookApp = outlookApp; } public bool IsMyPstStoreIsAvailable(string strStoreID) { foreach (IOutlookStore objStore in _outlookApp.Session.Stores) { if (objStore.StoreID == strStoreID) return true; } return false; } }
Step 3: Write Unit Tests with Moq
Now we can use Moq to mock our abstractions, covering both scenarios: when the store exists (returns true) and when it doesn’t (returns false). I’ll use xUnit for the test framework, but this works with NUnit or MSTest too.
using Moq; using Xunit; public class OutlookStoreCheckerTests { [Fact] public void IsMyPstStoreIsAvailable_StoreExists_ReturnsTrue() { // Arrange string targetStoreId = "MyTargetPSTStoreID"; // Mock a store that matches our target ID var mockMatchingStore = new Mock<IOutlookStore>(); mockMatchingStore.Setup(store => store.StoreID).Returns(targetStoreId); // Mock some other stores to simulate a real session var mockStore1 = new Mock<IOutlookStore>(); mockStore1.Setup(store => store.StoreID).Returns("RandomStoreID1"); var mockStore2 = new Mock<IOutlookStore>(); mockStore2.Setup(store => store.StoreID).Returns("RandomStoreID2"); // Mock the session and populate its Stores collection var mockSession = new Mock<IOutlookSession>(); mockSession.Setup(session => session.Stores) .Returns(new List<IOutlookStore> { mockStore1.Object, mockMatchingStore.Object, mockStore2.Object }); // Mock the application to return our mocked session var mockApp = new Mock<IOutlookApplication>(); mockApp.Setup(app => app.Session).Returns(mockSession.Object); // Create our service with the mocked dependencies var storeChecker = new OutlookStoreChecker(mockApp.Object); // Act bool result = storeChecker.IsMyPstStoreIsAvailable(targetStoreId); // Assert Assert.True(result); } [Fact] public void IsMyPstStoreIsAvailable_StoreDoesNotExist_ReturnsFalse() { // Arrange string targetStoreId = "NonExistentStoreID"; // Mock stores that don't match our target ID var mockStore1 = new Mock<IOutlookStore>(); mockStore1.Setup(store => store.StoreID).Returns("ExistingStoreID1"); var mockStore2 = new Mock<IOutlookStore>(); mockStore2.Setup(store => store.StoreID).Returns("ExistingStoreID2"); var mockSession = new Mock<IOutlookSession>(); mockSession.Setup(session => session.Stores) .Returns(new List<IOutlookStore> { mockStore1.Object, mockStore2.Object }); var mockApp = new Mock<IOutlookApplication>(); mockApp.Setup(app => app.Session).Returns(mockSession.Object); var storeChecker = new OutlookStoreChecker(mockApp.Object); // Act bool result = storeChecker.IsMyPstStoreIsAvailable(targetStoreId); // Assert Assert.False(result); } }
Why This Works
By abstracting the Outlook objects, we’ve decoupled our business logic from the concrete COM implementation. Now we can fully control the Stores collection in tests: we can add stores with matching IDs to test the return true path, and omit the target ID to test the full loop + return false path.
If you can’t do full refactoring for some reason, paid tools like TypeMock or JustMock can mock sealed COM objects directly—but the abstraction approach is cleaner, more maintainable, and uses free, open-source tools.
内容的提问来源于stack exchange,提问作者Pratik Ratanpara

