From da86bd7c7b1e1dd29e2cb379fc41db07b8655c53 Mon Sep 17 00:00:00 2001 From: Robert Baillie Date: Fri, 1 Apr 2022 11:18:49 +0100 Subject: [PATCH] Added tests for the defaulting of service implementations based on their name --- .../classes/common/fflib_Application.cls | 23 ++- .../common/tests/fflib_ApplicationTest.cls | 137 ++++++++++++++++-- 2 files changed, 139 insertions(+), 21 deletions(-) diff --git a/framework/default/fflib/default/classes/common/fflib_Application.cls b/framework/default/fflib/default/classes/common/fflib_Application.cls index 6cca785ffd8..30353c99e06 100644 --- a/framework/default/fflib/default/classes/common/fflib_Application.cls +++ b/framework/default/fflib/default/classes/common/fflib_Application.cls @@ -158,22 +158,29 @@ public virtual class fflib_Application public virtual Object newInstance(Type serviceInterfaceType) { // Mock implementation? - if(m_serviceInterfaceTypeByMockService.containsKey(serviceInterfaceType)) + if( m_serviceInterfaceTypeByMockService.containsKey( serviceInterfaceType ) ) { - return m_serviceInterfaceTypeByMockService.get(serviceInterfaceType); + return m_serviceInterfaceTypeByMockService.get( serviceInterfaceType ); } - // Create an instance of the type implementing the given interface + String serviceInterfaceName = serviceInterfaceType.getName(); + + // Check if a configured implementation exists in the Custom Metadata Type serviceImpl = m_serviceInterfaceTypeByServiceImplType.get(serviceInterfaceType); - if ( serviceImpl != null ) { - return serviceImpl.newInstance(); + if ( serviceImpl != null ) + { + try { + return serviceImpl.newInstance(); + } + catch ( Exception e ) { + throw new DeveloperException( 'Implementation for service interface ' + serviceInterfaceName + ' (' + serviceImpl.getName() + ') could not be constructed', e ); + } } - String serviceInterfaceName = serviceInterfaceType.getName(); - + // Check if we can use a default service instead, based on the name IServiceInterface => ServiceInterfaceImpl if ( ! isAServiceInterfaceName( serviceInterfaceName ) ) { - throw new DeveloperException( 'No implementation registered for service interface ' + serviceInterfaceName + ' and default implementation cannot be determined from the name (not an interface in the standard naming convention' ); + throw new DeveloperException( 'No implementation registered for service interface ' + serviceInterfaceName + ' and default implementation cannot be determined from the name (not an interface in the standard naming convention (Ixxxx)' ); } String defaultServiceName = buildDefaultServiceName( serviceInterfaceName ); diff --git a/framework/default/fflib/default/classes/common/tests/fflib_ApplicationTest.cls b/framework/default/fflib/default/classes/common/tests/fflib_ApplicationTest.cls index af1dd15274f..d9fd22033e6 100644 --- a/framework/default/fflib/default/classes/common/tests/fflib_ApplicationTest.cls +++ b/framework/default/fflib/default/classes/common/tests/fflib_ApplicationTest.cls @@ -235,24 +235,135 @@ private class fflib_ApplicationTest } @IsTest - private static void callingServiceFactoryShouldGiveRegisteredImplsAndMockImpls() + private static void callingServiceFactoryShouldGiveRegisteredImplementationAndMockOnes() { // Standard behaviour - System.assert(Service.newInstance(IAccountService.class) instanceof AccountsServiceImpl); - System.assert(Service.newInstance(IOpportunitiesService.class) instanceof OpportunitiesServiceImpl); - try { - Service.newInstance(IContactService.class); - System.assert(false, 'Expected exception'); - } catch (fflib_Application.DeveloperException e) { - System.assertEquals('No implementation registered for service interface ' + IContactService.class.getName(), e.getMessage()); - } + System.assert(Service.newInstance(IRegisteredServiceWithStandardName1.class) instanceof RegisteredServiceWithStandardName1Impl, 'ServiceFactory.newInstance will return registered implementations' ) ; + System.assert(Service.newInstance(IRegisteredServiceWithStandardName2.class) instanceof RegisteredServiceWithStandardName2Impl, 'ServiceFactory.newInstance will return registered implementations (2)' ); + System.assert(Service.newInstance(IRegisteredServiceWithNonStandardImplName.class) instanceof RegisteredServiceWithNonStandardNameImplName, 'ServiceFactory.newInstance will return registered implementations (3)' ); + System.assert(Service.newInstance(RegisteredServiceWithNonStandardName.class) instanceof RegisteredServiceWithNonStandardNameImpl, 'ServiceFactory.newInstance will return registered implementations (4)' ); + + // Mocking behaviour + Service.setMock(IRegisteredServiceWithStandardName1.class, new ServiceMock()); + System.assert(Service.newInstance(IRegisteredServiceWithStandardName1.class) instanceof ServiceMock,'ServiceFactory.newInstance, when a mock is registered, will return the mock for that type'); + System.assert(Service.newInstance(IRegisteredServiceWithStandardName2.class) instanceof RegisteredServiceWithStandardName2Impl, 'ServiceFactory.newInstance, when a mock is registered, will still return the standard one for other types (1)'); + System.assert(Service.newInstance(IRegisteredServiceWithNonStandardImplName.class) instanceof RegisteredServiceWithNonStandardNameImplName, 'ServiceFactory.newInstance, when a mock is registered, will still return the standard one for other types (2)'); + System.assert(Service.newInstance(RegisteredServiceWithNonStandardName.class) instanceof RegisteredServiceWithNonStandardNameImpl, 'ServiceFactory.newInstance, when a mock is registered, will still return the standard one for other types (3)'); + } + + @IsTest + private static void callingServiceFactoryShouldGiveDefaultImplementation() + { + // Standard behaviour + System.assert(Service.newInstance(IUnregisteredServiceWithStandardName.class) instanceof UnregisteredServiceWithStandardNameImpl, 'ServiceFactory.newInstance, when given a standard format interface that is not registered, and a standard implentation that exists, will return the standard service' ) ; + + // Mocking behaviour + Service.setMock(IUnregisteredServiceWithStandardName.class, new ServiceMock()); + System.assert(Service.newInstance(IUnregisteredServiceWithStandardName.class) instanceof ServiceMock, 'ServiceFactory.newInstance, when given a standard format interface that is not registered and a mock is set, will return the mock for that type'); + } - // Mocking behaviour - Service.setMock(IAccountService.class, new AccountsServiceMock()); - System.assert(Service.newInstance(IOpportunitiesService.class) instanceof OpportunitiesServiceImpl); - System.assert(Service.newInstance(IAccountService.class) instanceof AccountsServiceMock); + @isTest + private static void serviceFactory_whenAnUnregisteredInterfaceWithNoDefaultImpl_throwsAnException() // NOPMD: Test method name format + { + Test.startTest(); + String exceptionMessage; + try + { + Service.newInstance(IUnregisteredServiceWithNonStandardImpl.class); + } + catch ( Exception e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + System.assert( exceptionMessage.contains( 'No implementation registered for service interface' ), 'serviceFactory, when given a standard format interface that has no default implementation, will throw an exception' ); + System.assert( exceptionMessage.contains( 'and no default implementation found with the name' ), 'serviceFactory, when given a standard format interface that has no default implementation, will throw an exception' ); + } + + @isTest + private static void serviceFactory_whenAnUnregisteredInterfaceWithNoImpl_throwsAnException() // NOPMD: Test method name format + { + Test.startTest(); + String exceptionMessage; + try + { + Service.newInstance(IUnregisteredServiceWithNoImplementation.class); + } + catch ( Exception e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + System.assert( exceptionMessage.contains( 'No implementation registered for service interface' ), 'serviceFactory, when given a standard format interface that has no default implementation, will throw an exception' ); + System.assert( exceptionMessage.contains( 'and no default implementation found with the name' ), 'serviceFactory, when given a standard format interface that has no default implementation, will throw an exception' ); + } + + @isTest + private static void serviceFactory_whenAnUnregisteredInterfaceWithNonStandardName_throwsAnException() // NOPMD: Test method name format + { + Test.startTest(); + String exceptionMessage; + try + { + Service.newInstance(UnregisteredServiceWithNonStandardName.class); + } + catch ( Exception e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + System.assert( exceptionMessage.contains( 'No implementation registered for service interface' ), 'serviceFactory, when given a non standard format interface that is not registered, will throw an exception' ); + System.assert( exceptionMessage.contains( 'and default implementation cannot be determined from the name (not an interface in the standard naming convention (Ixxxx)' ), 'serviceFactory, when given a standard format interface that is not registered, will throw an exception' ); } + @isTest + private static void serviceFactory_whenARegisteredInterfaceCannotBeConstructed_throwsAnException() // NOPMD: Test method name format + { + Test.startTest(); + String exceptionMessage = ''; + try + { + Service.newInstance(IRegisteredServiceUnconstructable.class); + } + catch ( Exception e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + System.assert( exceptionMessage.contains( 'Implementation for service interface' ), 'serviceFactory, when given registered service that cannot be constructed, will throw an exception' ); + System.assert( exceptionMessage.contains( 'could not be constructed' ), 'serviceFactory, when given registered service that cannot be constructed, will throw an exception' ); + } + + @isTest + private static void serviceFactory_whenAnUnregisteredInterfaceCannotBeConstructed_throwsAnException() // NOPMD: Test method name format + { + Test.startTest(); + String exceptionMessage = ''; + try + { + Service.newInstance(IUnregisteredServiceUnconstructable.class); + } + catch ( Exception e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + System.assert( exceptionMessage.contains( 'Default implementation for service interface' ), 'serviceFactory, when given unregistered service that cannot be constructed, will throw an exception' ); + System.assert( exceptionMessage.contains( 'could not be constructed' ), 'serviceFactory, when given unregistered service that cannot be constructed, will throw an exception' ); + } + + // TODO: test isAServiceInterfaceName directly + // top level interface with no namespace + // top level interface with a namespace + // inner interface with no namespace + // inner interface with a namespace + // TODO: test buildDefaultServiceName + @IsTest private static void callingSelectorFactoryShouldGiveRegisteredImpls() {