Introduction
This article can be useful when unit testing application-layers. When unit testing, the output should be well known to be tested. This can be achieved with stubs. In the stub, you can create the output you want to let the test pass. Nonetheless, stubs should not be called within the production environment, of course. How then do we switch 'on' the stub when unit testing, and 'off' when being in production? This article describes a way of dynamically loading an assembly by configuring it in a config-file.
To use the demo, be sure to have NUnit installed.
Using the code
The main class to use is ServiceProviderFactory
. This factory activates the DLLs in its Create
method. Before it can do that, it has to be configured by calling the Configure
method. This is usually done in the constructor. The following code demonstrates this:
ServiceProviderFactory.Configure(
ResourceHelper.GetResourceFile(typeof(WalletUt).Assembly,
"configFile.xml"));
Note that it uses a resourceHelper
. This helper loads resources embedded in the assembly. In the above line of code, the helper loads the config.xml file from the WalletUt-assembly.
An assembly can be created using the Create
method on the name it's given in the config.xml file or its type. The following shows this:
IProcesLayer _wallet = (IProcesLayer)
ServiceProviderFactory.Create("Wallet");
IProcesLayer _wallet = (IProcesLayer)
ServiceProviderFactory.Create(typeof(IProcesLayer));
Internal workings
To use a specific format for the config.xml file, we can validate this against a schema. To create the schema, I first created the XML-file and then created the schema with the XSD tool, which is part of the Visual Studio environment. This schema can than be used together with XmlValidatingReader
to validate the config.xml file. The following screenshot is a short view of making the schema with the XSD tool.
The config.xml file contains ServiceProviders. A ServiceProvider is defined in the class ServiceProvider
. This class contains the type
('namespace.class' 'assembly-name') of the assembly to load. It also has an InterfaceType
which can be specified. This is useful to have an interface specified, and this interface can be in a different assembly as is done in the demo. This way, the interface can be shared among other projects, and therefore the assembly itself does not have to be referenced.
The ServiceProvider
contains an InitializeData
parameter which is useful to pass on some data for initialization. This way, a stub can be used from different (test)-projects, each expecting a different result because the InitializeData
is different. Very powerful! Now, the stub does not have to be programmed with different if
s to filter out different program-flows.
The last parameter, ServiceProvider
, is the name of the ServiceProvider. This is not just to make each ServiceProvider unique. It can also be used to create the assembly. Creating the assembly can be on its interface or on the name you have given it in the config-file. This is done because there might be more classes with the same interface. So giving these a different name, you are able to load these as well.
The demo-application has the following config.xml file for its proceslayertest application:
="1.0" ="utf-8" <serviceproviders
xmlns= "urn://YourCompany.org/XMLSchema1.xsd">
<serviceproviders>
<serviceprovider interfacetype="Interfaces.IProcesLayer, Interfaces"
type="ProcesLayer.Wallet, ProcesLayer">
<name>Wallet</name>
</serviceprovider>
<serviceprovider initializedata="150"
interfacetype="Interfaces.IServiceLayer, Interfaces"
type="ServiceLayer.ServiceStub, ServiceLayer.Stub">
<name>ServiceStubLayer</name>
</serviceprovider>
</serviceproviders>
</serviceproviders>
The following config.xml file is for the 'production' application, the Windows application:
="1.0" ="utf-8"
<serviceproviders
xmlns= "urn://YourCompany.org/XMLSchema1.xsd">
<serviceproviders>
<serviceprovider interfacetype="Interfaces.IProcesLayer, Interfaces"
type="ProcesLayer.Wallet, ProcesLayer">
<name>Wallet</name>
</serviceprovider>
<serviceprovider interfacetype="Interfaces.IServiceLayer, Interfaces"
type="ServiceLayer.Service, ServiceLayer">
<name>ServiceLayer</name>
</serviceprovider>
</serviceproviders>
</serviceproviders>
It should be clear from these two samples that only the service layer is switched. The same process layer is both called from the 'production' application and the ProceslayerTest application.
An array of ServiceProvider
is embedded in ServiceProviders
. Notice the internal namespace (= urn://YourCompany.org/XMLSchema1.xsd) being equal to the namespace in the config file. This namespace should be carefully chosen. Preferably, pick your company name.
In the test environment, the serviceLayer-serviceprovider has the type "ServiceLayer.ServiceStub, ServiceLayer.Stub". Whereas in the 'production' environment, this same provider has the type "ServiceLayer.Service, ServiceLayer". This is the switch I'm talking about. This can be redirected to another layer.
Points of Interest
Using a NMock-object could be another approach in testing layers in your application.
It is interesting to use an initialize data parameter in a production-environment. By setting the parameter, you can switch, for example, tracing on and off.
History
- Version 1.0, 14 February 2006.