diff --git a/src/XrmMockupShared/MockupServiceProviderAndFactory.cs b/src/XrmMockupShared/MockupServiceProviderAndFactory.cs index ddd41067..d59b834c 100644 --- a/src/XrmMockupShared/MockupServiceProviderAndFactory.cs +++ b/src/XrmMockupShared/MockupServiceProviderAndFactory.cs @@ -6,7 +6,8 @@ using System.Text; using System.Threading.Tasks; -namespace DG.Tools.XrmMockup { +namespace DG.Tools.XrmMockup +{ /// /// A factory used to generate MockupServices based on a given Mockup instance and pluginContext @@ -16,6 +17,7 @@ internal class MockupServiceProviderAndFactory : IServiceProvider, IOrganization private Core core; private ITracingService tracingService; private PluginContext pluginContext; + private Dictionary mockServices; /// /// Creates new MockupServiceProviderAndFactory object @@ -23,14 +25,16 @@ internal class MockupServiceProviderAndFactory : IServiceProvider, IOrganization /// public MockupServiceProviderAndFactory(Core core) : this(core, null, new TracingService()) { } - internal MockupServiceProviderAndFactory(Core core, PluginContext pluginContext, ITracingService tracingService) { + internal MockupServiceProviderAndFactory(Core core, PluginContext pluginContext, ITracingService tracingService) + { this.core = core; this.pluginContext = pluginContext; this.tracingService = tracingService; + this.mockServices = core.ServiceFactory?.mockServices ?? new Dictionary(); } /// - /// Get service from servicetype. Returns null if unknown type + /// Get service from servicetype. Returns null if unknown type, new types can be added with if needed. /// /// /// @@ -38,7 +42,49 @@ public object GetService(Type serviceType) { if (serviceType == typeof(IPluginExecutionContext)) return this.pluginContext; if (serviceType == typeof(ITracingService)) return this.tracingService; if (serviceType == typeof(IOrganizationServiceFactory)) return this; - return null; + + mockServices.TryGetValue(serviceType, out object customService); + + if(customService == null) + { + var errorMessage = $"No service with the type {serviceType} found.\n" + + $"Only {nameof(IPluginExecutionContext)}, {nameof(ITracingService)} and {nameof(IOrganizationServiceFactory)} are supported by default.\n" + + $"Other mock services need to be registered in the mock crm context by yourself."; + throw new KeyNotFoundException(errorMessage); + } + + return customService; + } + + /// + /// Add a custom mock service. + /// This will not override the default XrmMockup services. + /// + /// + /// + public void AddCustomService(T service) + { + mockServices.Add(typeof(T), service); + } + + /// + /// Remove a custom mock service. + /// This will not affect the default XrmMockup services. + /// + /// + /// + public void RemoveCustomService() + { + mockServices.Remove(typeof(T)); + } + + /// + /// Clear all custom mock services. + /// This will not affect the default XrmMockup services. + /// + public void ClearCustomServices() + { + mockServices.Clear(); } /// diff --git a/src/XrmMockupShared/XrmMockupBase.cs b/src/XrmMockupShared/XrmMockupBase.cs index 07c75990..2b571ac7 100644 --- a/src/XrmMockupShared/XrmMockupBase.cs +++ b/src/XrmMockupShared/XrmMockupBase.cs @@ -220,6 +220,37 @@ public void PopulateWith(params Entity[] entities) { Core.PopulateWith(entities); } + /// + /// Add a custom mock service. + /// This will not override the default XrmMockup services. + /// + /// e.g. IServiceEndpointNotificationService + /// + public void AddService(T service) + { + ServiceFactory.AddCustomService(service); + } + + /// + /// Remove a custom mock service. + /// This will not override the default XrmMockup services. + /// + /// e.g. IServiceEndpointNotificationService + /// + public void RemoveService() + { + ServiceFactory.RemoveCustomService(); + } + + /// + /// Clear all custom mock services. + /// This will not affect the default XrmMockup services. + /// + public void ResetServices() + { + ServiceFactory.ClearCustomServices(); + } + /// /// Gets a system administrator organization service /// diff --git a/tests/SharedPluginsAndCodeactivites/ContactSendToServiceEndpointPlugin.cs b/tests/SharedPluginsAndCodeactivites/ContactSendToServiceEndpointPlugin.cs new file mode 100644 index 00000000..7e801641 --- /dev/null +++ b/tests/SharedPluginsAndCodeactivites/ContactSendToServiceEndpointPlugin.cs @@ -0,0 +1,35 @@ +using DG.Some.Namespace; +using DG.XrmFramework.BusinessDomain.ServiceContext; +using Microsoft.Xrm.Sdk; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedPluginsAndCodeactivites +{ + /// + /// This plugin is used to test custom mock service registrations, the actual endpoint id doesn't matter here + /// + public class SendContactToServiceEndpointPlugin : Plugin + { + public SendContactToServiceEndpointPlugin() : base(typeof(SendContactToServiceEndpointPlugin)) + { + RegisterPluginStep(EventOperation.Create, ExecutionStage.PreOperation, Execute) + .SetExecutionMode(ExecutionMode.Synchronous); + } + + protected void Execute(LocalPluginContext localContext) + { + var target = localContext.PluginExecutionContext.InputParameters["Target"] as Entity; + var contact = target.ToEntity(); + + if (contact.Description == "Test_IServiceEndpointNotificationService") + { + var serviceEndpoint = (IServiceEndpointNotificationService)localContext.ServiceProvider.GetService(typeof(IServiceEndpointNotificationService)); + var endpointReference = new EntityReference("serviceendpoint", Guid.Empty); + + serviceEndpoint.Execute(endpointReference, localContext.PluginExecutionContext); + } + } + } +} diff --git a/tests/SharedPluginsAndCodeactivites/Plugin.cs b/tests/SharedPluginsAndCodeactivites/Plugin.cs index d0ea3fd7..99b6eb01 100644 --- a/tests/SharedPluginsAndCodeactivites/Plugin.cs +++ b/tests/SharedPluginsAndCodeactivites/Plugin.cs @@ -71,6 +71,8 @@ internal LocalPluginContext(IServiceProvider serviceProvider) throw new ArgumentNullException("serviceProvider"); } + this.ServiceProvider = serviceProvider; + // Obtain the execution context service from the service provider. this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); diff --git a/tests/SharedPluginsAndCodeactivites/SharedPluginsAndCodeactivites.projitems b/tests/SharedPluginsAndCodeactivites/SharedPluginsAndCodeactivites.projitems index 11c7fc45..a2860d0f 100644 --- a/tests/SharedPluginsAndCodeactivites/SharedPluginsAndCodeactivites.projitems +++ b/tests/SharedPluginsAndCodeactivites/SharedPluginsAndCodeactivites.projitems @@ -10,6 +10,7 @@ + diff --git a/tests/SharedTests/SharedTests.projitems b/tests/SharedTests/SharedTests.projitems index b1c08505..5183a341 100644 --- a/tests/SharedTests/SharedTests.projitems +++ b/tests/SharedTests/SharedTests.projitems @@ -17,6 +17,7 @@ + diff --git a/tests/SharedTests/TestCustomService.cs b/tests/SharedTests/TestCustomService.cs new file mode 100644 index 00000000..1a71971e --- /dev/null +++ b/tests/SharedTests/TestCustomService.cs @@ -0,0 +1,94 @@ +using Xunit; +using Microsoft.Xrm.Sdk; +using DG.XrmFramework.BusinessDomain.ServiceContext; +using System.Collections.Generic; + +namespace DG.XrmMockupTest +{ + public class TestCustomService : UnitTestBase + { + class MockServiceEndpointNotificationService : IServiceEndpointNotificationService + { + public string Execute(EntityReference serviceEndpoint, IExecutionContext context) + { + return null; + } + } + + private const string contactDescription = "Test_IServiceEndpointNotificationService"; + + public TestCustomService(XrmMockupFixture fixture) : base(fixture) + { + crm.ResetServices(); + } + + [Fact] + public void CustomServiceShouldBeAvailableInPlugin() + { + var customService = new MockServiceEndpointNotificationService(); + crm.AddService(customService); + + orgAdminUIService.Create(new Contact() { Description = contactDescription }); + } + + [Fact] + public void ShouldThrowExceptionOnDuplicateRegistration() + { + var customService = new MockServiceEndpointNotificationService(); + crm.AddService(customService); + + Assert.Throws(() => + { + var customService2 = new MockServiceEndpointNotificationService(); + crm.AddService(customService2); + }); + } + + [Fact] + public void ShouldThrowKeyNotFoundExceptionIfServiceIsNotFound() + { + Assert.Throws(() => + { + orgAdminUIService.Create(new Contact() { Description = contactDescription }); + }); + + crm.ResetServices(); + } + + [Fact] + public void ShouldThrowKeyNotFoundExceptionIfServiceIsRemoved() + { + var customService = new MockServiceEndpointNotificationService(); + crm.AddService(customService); + crm.RemoveService(); + + Assert.Throws(() => + { + orgAdminUIService.Create(new Contact() { Description = contactDescription }); + }); + } + + [Fact] + public void ShouldThrowKeyNotFoundExceptionIfServicesAreReset() + { + var customService = new MockServiceEndpointNotificationService(); + crm.AddService(customService); + crm.ResetServices(); + + Assert.Throws(() => + { + orgAdminUIService.Create(new Contact() { Description = contactDescription }); + }); + } + + [Fact] + public void CustomServiceShouldStayAvailableAfterEnvironmentReset() + { + var customService = new MockServiceEndpointNotificationService(); + crm.AddService(customService); + crm.ResetEnvironment(); + + orgAdminUIService.Create(new Contact() { Description = "Test_IServiceEndpointNotificationService" }); + } + } +}