Results 1 to 3 of 3

Thread: Dynamic runtime object definitions

  1. #1
    Join Date
    Mar 2007
    Posts
    2

    Default Dynamic runtime object definitions

    What if you have to (or just want to) dynamically change an existing (singleton) object definition in code at runtime?

    Adding a new object definition is explained in Programmatic Add of an Object to a Context.

    But what if the object definition (id) already exists? For example: Solution A has a MyApp assembly with Spring configuration in one App.config, and a separate MyUnitTesting assembly with a separate Spring configuration in UnitTesting's App.config. MyApp has class MyClass, which needs to be mocked up for testing purposes, so MyUnitTesting has MockMyClass. MyClass and MockMyClass are defined in both Spring configs with the same name "idMyClass", so when MyApp is started directly, it will get the MyClass object, whereas when started with MyUnitTesting, it gets the MockMyClass object.

    But now it's time to write tests for the "real" MyClass, and you want to maintain the separation between MyApp and MyUnitTesting. Wouldn't it be nice to dynamically, at runtime, in the test code itself, redefine "idMyClass" to reference MyApp.MyClass, without changing the reference for the rest of the tests?

    I struggled with how to do this without requiring any changes to the existing Spring configuration, just to allow this one case, and I came up with a solution. Perhaps there is another way - please let me know if there is, because this is a hack that I have made to look somewhat elegant...

    Essentially I use a similar method as when adding a new definition, but via the IConfigurableListableObjectFactory cast to a DefaultListableObjectFactory, using its RegisterObjectDefinition method, which allows (by default, AllowObjectDefinitionOverriding == true) overriding object definitions. This, together with Aleks Seovic's method of creating a new object definition, is all you need for non-singleton objects. But singletons are cached at the time the context is instantiated, and so this cached singleton must be cleared. This required a reflection trick to access and modify the private IDictionary.

    I've packaged this up in the SpringUtil class below. Just use one of the ReplaceObjectDefinition methods. Even better, do your testing or whatever other operation with a "using" block with a TemporaryObjectDefinition object, and the original object definition (but not the cached singleton instance! -- though it could easily be rewritten to accomplish even this) will be restored when the using block falls out of scope.

    Like so:

    Code:
    //do some testing (the original object definition from the App.config is being used)
    
    using (new SpringUtil.TemporaryObjectDefinition("idMyClass", "MyClass, MyApp"))
    {
        //do some testing (the temporary object defintion will be used)
    }
    
    //do some more testing (the original object definition from the App.config will be used again)
    Let me know what you think or if there is a better way.

    Hope this helps.

    Code:
    using System;
    using System.Collections;
    using System.Text;
    using System.Reflection;
    
    using Spring.Context.Support;
    using Spring.Objects.Factory.Support;
    using Spring.Objects.Factory.Config;
    using Spring.Objects;
    
    namespace org.in2bits
    {
        public static class SpringUtil
        {
            public class TemporaryObjectDefinition : IDisposable
            {
                private string _objectId;
                private IObjectDefinition _exisitingObjectDefinition;
    
                public TemporaryObjectDefinition(string objectId, string newObjectType)
                {
                    _objectId = objectId;
                    _exisitingObjectDefinition = ContextRegistry.GetContext().GetObjectDefinition(_objectId);
                    ReplaceObjectDefinition(_objectId, newObjectType);
                }
    
                #region IDisposable Members
    
                public void Dispose()
                {
                    ReplaceObjectDefinition(_objectId, _exisitingObjectDefinition);
                }
    
                #endregion
            }
    
            public static void ReplaceObjectDefinition(string objectId, string newObjectType)
            {
                IObjectDefinition existingObjectDefinition = ContextRegistry.GetContext().GetObjectDefinition(objectId);
                ReplaceObjectDefinition(objectId, newObjectType, existingObjectDefinition.IsLazyInit, existingObjectDefinition.IsSingleton);
            }
            
            public static void ReplaceObjectDefinition(string objectId, string newObjectType, bool forceLazyInit, bool forceSingleton)
            {
                ReplaceObjectDefinition(objectId, GetNewObjectDefinition(newObjectType, forceLazyInit, forceSingleton));
            }
    
            public static void ReplaceObjectDefinition(string objectId, IObjectDefinition newObjectDefinition)
            {
                XmlApplicationContext xmlApplicationContext = ContextRegistry.GetContext() as XmlApplicationContext;
                DefaultListableObjectFactory objectFactory = xmlApplicationContext.ObjectFactory as DefaultListableObjectFactory;
    
                objectFactory.RegisterObjectDefinition(objectId, newObjectDefinition);
    
                ClearSingletonCache(objectFactory, objectId);
            }
    
            private static void ClearSingletonCache(DefaultListableObjectFactory objectFactory, string objectId)
            {
                Type factoryType = objectFactory.GetType().BaseType.BaseType;
                FieldInfo fieldInfo = factoryType.GetField("singletonCache", BindingFlags.Instance | BindingFlags.NonPublic);
                IDictionary dictionary = (IDictionary)fieldInfo.GetValue(objectFactory);
    
                if (dictionary.Contains(objectId))
                    dictionary.Remove(objectId);
            }
    
            private static IObjectDefinition GetNewObjectDefinition(string objectType, bool forceLazyInit, bool forceSingleton)
            {
                DefaultObjectDefinitionFactory objectDefinitionFactory = new DefaultObjectDefinitionFactory();
    
                ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
                MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
                
                IConfigurableObjectDefinition objectDefinition = objectDefinitionFactory.CreateObjectDefinition(
                    objectType,
                    null,
                    constructorArgumentValues,
                    mutablePropertyValues,
                    AppDomain.CurrentDomain);
    
                objectDefinition.IsLazyInit = forceLazyInit;
                objectDefinition.IsSingleton = forceSingleton;
    
                return objectDefinition;
            }
        }
    }
    Last edited by Manmoth; 03-07-2007 at 08:43 PM.

  2. #2
    Join Date
    Sep 2004
    Location
    Belgrade, Serbia
    Posts
    613

    Default

    Just to clarify something -- Spring shouldn't be used at all within unit tests, only within the integration tests. Unit tests should simply inject properly configured dependencies into the class under test using its constructor and/or property setters.

    That said, being able to replace objects at runtime can be useful for integration testing, which I assume is what you are describing. I do have a few suggesstions for improvement, though:

    1. At the moment, you can only replace definitions in the root context, as you are relying on the ContextRegistry.GetContext() call to get to the target context. Better approach would be to explicitly create the context within the test and pass it to your replacement method. Alternatively, you can create the context using standard .NET configuration, as you are doing now, but allow users to pass context name to your methods.

    2. The current approach requires context to be registered with the ContextRegistry. I would probably allow either context instance or context name to be passed to the replacement method. That way, you could use context directly if the instance was specified, or retrieve it from the registry if the name was specified.

    3. I'm not sure why you need object definition for a singleton instance. I would just create and configure temporary instance, one way or another (for example, you could load a separate context that will create it based on the test config file and use GetObject() to get the from it, or you can just instantiate and configure replacement object directly). Then, you could simply call IConfigurableObjectFactory.RegisterSingleton method, passing object id and replacement instance as arguments. This will also allow you to get rid of the reflection code that depends on Spring internals. (BTW, you still need object definition replacement for prototypes, but that's simple enough).

    Other than that, I like the idea a lot and how you used IDisposable to reset definition to the original one after you are done. With some polishing, this would definitely be a useful addition to Spring.Testing module.

    Regards,

    Aleks
    Last edited by Aleks Seovic; 03-08-2007 at 02:02 PM.

  3. #3
    Join Date
    Mar 2007
    Posts
    2

    Default

    Thanks, Aleks, I agree with most everything you say about improvements to my code. I spent only about an hour on it once I realized what I wanted it to do, and so didn't spend any more on generalizing it for broader use. In terms of the Unit vs. Integration testing, we do both with NUnit, and those tests are not distinguished in the same assembly - UnitTesting. I was careless with my vocabulary (and am fairly new to testing).

    Otherwise, I am sure there are better ways to handle this type of thing using different contexts. However, being new to both testing architecture patterns and to Spring (or DI generally), at the time I designed the architecture I didn't know enough to make choices that would have made testing easier.

    Even still, though, I prefer not to have to write any more code than necessary for Tests (Unit or Integration), both for ease of writing and for clarity of understanding. Since this was the exception, rather than the rule, having to test this module that was otherwise inaccessible, this worked very well for me (albeit at the risk of biting me if ever singletonCache is renamed/moved).

    After considering your answer and reconsidering my own architecture, I still think I want this to be a singleton. If you're curious why, I could elaborate, though that may be better on another thread. And I don't want to have to worry about creating and managing separate contexts in Test code, and certainly not in App (non-test) code.

    Be that as it may, what may be more constructive to discuss is exactly these patters of using Spring in various testing vs. production scenarios. Perhaps Spring was intended more primarily for things other than allowing the same code to be configured differently for production vs. for testing (i.e. removing much of the otherwise necessary code architecture changes to allow Testing - with test resources or mock objects, etc.). But this is exactly the scenario for which it was presented to me, and so that is how I have been approaching it.

    If Spring is intended to fulfill this type of function, then it would be nice to have things more pertinent to this layed out in the documentation (I saw no mention at all of "test" in it). Additionally or alternatively, exposing some pertinent methods or properties on the IApplicationContext or IObjectFactory interfaces would be helpful.

    Even to replace a prototype definition requires casting to an IApplicationContext implementation class (XmlApplicationContext in my case) to get the ObjectFactory. Then this is an IObjectFactory, which has to itself be cast to an IObjectFactory implementation class (DefaultListableObjectFactory in my case) in order to execute a RegisterObjectDefinition. This is regardless of singleton vs. prototype.

    Casting from interfaces to implementation classes is not elegant or intuitive, in my view, and another reason I want to keep it out of my Test code. This is the type of thing I immediately put in a wrapper function or class. But I ask myself when I have to, "Why do I have to do this?"

    I remain unconvinced that this is such a rare way of using Spring, as this is one of the very first things I asked myself when I first approached it to use it: "How do I test the 'real' vs. the 'mock' object in my tests?" I read the documentation and scoured Google to find answers, but found no real mention. And why would I immediately jump into figuring out creating and manipulating contexts, adding another layer of complexity, if there was no clear indication that this is the only or best way to address this issue with Spring?

    Sorry if this turned into a rant, but I'm gonna stick with my solution until I'm convinced I'm better off otherwise. I don't like the hack, but that's all I'm left with if I want to maintain my object's singleton status and am not going to dive nose-first into manipulating contexts, not only in Test code, but in all likelihood in application code as well.

    I'm open to better answers. At least, I think
    Last edited by Manmoth; 03-08-2007 at 04:18 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •