locked
Replacing parts of a workflow with mocks for unit testing RRS feed

  • Question

  • Is it possible to intercept calls to object creation when a XAML workflow is being run?

    Say I have a workflow and I want to unit test it. The workflow is simple:
        - Sequence
            - Task A
            - Task B

    And let's say that Task A is a separate XAML workflow that takes a long time to run and that I'm not interested in for this particular unit test. So I'd like to substitute a mock object in its place.

    My first thought was that there was a way I could influence the Xaml reader to say how it maps from the type string to the actual type. I couldn't find a way of doing this.

    My next thought was to use a TypeDescriptionProvider. I thought all non-constructor object creation calls went through this subsystem, but apparently not. In this case, the TypeDescriptionProvider is never used.

    It seems that WF4 calls (sooner or later) System.RuntimeType.CreateInstanceSlow () to do the actual object creation, and there doesn't seem to be a way to intercept that.

    Am I missing something? Is there a way I can plug mock objects into the workflow object tree for my unit tests?

    Many thanks,

        Geoff

    Tuesday, November 24, 2009 12:11 PM

Answers

  • Your overflow appears to be because MockCodeActivity1 inherits from CodeActivity1 instead of from say CodeActivity.  I guess when it tries to resolve MockCodeActivity1, it will try to resolve CodeActivity1, which causes it to resolve MockCodeActivity1 again, ad infinitum.

    I'll try to find out whether this is expected behavior or not.
    Tuesday, December 1, 2009 1:12 PM
  • This is expected. There are a number of times in XAML parsing where we need to look up a type's base type. If it returns itself as its base, then that will produce either an infinite loop or a stack overflow.

    Whether the mocking type needs to be derived from the mocked type depends on how you're using it. If you're just doing something like:

    <Sequence ...
       <MyActivity ...

    Then the only requirement is that MyActivity be derived from Activity (since Sequence's content property is a collection of Activity).

    If you do actually need to derive from the mocked class, then you can work around this by overriding XamlType.LookupBaseType, and returning the actual mocked type.
    Tuesday, December 1, 2009 6:50 PM

All replies






  • MorphHelper.MorphObject(oldModelItem, newModelitem);

    Is DesignTime experience acceptable? You can do so using the above mentioned API.

    Sample blog post explains this:

    http://blogs.msdn.com/kushals/archive/2009/11/11/morphing.aspx

    Thanks,
    Kushal.

    Kushal Shah - This posting is provided "AS IS" with no warranties, and confers no rights
    • Proposed as answer by kushals Tuesday, November 24, 2009 4:49 PM
    • Unproposed as answer by OpinionatedGeek Tuesday, November 24, 2009 6:08 PM
    Tuesday, November 24, 2009 4:49 PM
  • Hi,

    I'm afraid not - I wanted to keep the same Xaml files I use for the real workflow and just substitute some simple mock objects for the bits I wasn't explicitly testing in a particular unit test.

    I really like the Morph stuff - thanks for the link! - but I don't think I can use it here.

    Cheers,

        Geoff
    Tuesday, November 24, 2009 6:11 PM
  • If you are in control of the XAML loading process, you could inject a rewriting (custom) XAML reader into the Xaml reading pipeline. If the ViewStateCleaningWriter made it into Beta2 (under WF\Basic\Designer), you could have a look at that as a guide.

    Does this look any use?

    Tim

    Tuesday, November 24, 2009 7:07 PM
  • This should certainly help then. Just write an overriden SchemaContext as follows:

    public class MySchemaContext : XamlSchemaContext
    {
    public override GetXamlType(Type type)
    {
    if (type == typeof(MyRealActivity))
    {
    return base.GetXamlType(typeof(MyMockActivity));
    }
                    else
                    {
                                    return base.GetXamlType(type);
                    }
    }
    }
    
    Then call ActivityXamlServices.Load(new XamlXmlReader(filename, new MySchemaContext()))
    

    Kushal Shah - This posting is provided "AS IS" with no warranties, and confers no rights
    • Proposed as answer by kushals Tuesday, November 24, 2009 8:04 PM
    • Unproposed as answer by OpinionatedGeek Monday, November 30, 2009 10:43 AM
    Tuesday, November 24, 2009 8:04 PM
  • Hi there,

    Thanks for that, and sorry it has taken a few days for me to get back to this.

    I did as you suggested and wrote a trivial SchemaContext. I wanted to be able to re-use it, so I pass in the types it should substitute:

    namespace OpinionatedGeek.Applications.Twoldem.Tests
    {
        using System;
        using System.Collections.Generic;
        using System.Xaml;
    
        public class MockingSchemaContext : XamlSchemaContext
        {
            private readonly Dictionary<Type, Type> _mocked = new Dictionary<Type, Type> ();
    
            public void AddMockedType (Type typeInXaml, Type typeToUse)
            {
                _mocked [typeInXaml] = typeToUse;
    
                return;
            }
    
            public override XamlType GetXamlType (Type type)
            {
                Type typeToUse = type;
                if (_mocked.ContainsKey (type))
                {
                    typeToUse = _mocked [type];
                }
    
                return base.GetXamlType (typeToUse);
            }
        }
    }
    Then I created a derived version of the code activity I wanted to substitute (so that the substituted class actually is of a compatible type).

        public class MockAudit : Audit
        {
            protected override void Execute (CodeActivityContext context)
            {
                return;
            }        
        }
    

    However, when I try and use it (calling ActivityXamlServices.Load) I get a stack overflow exception. The test executes as expected when I don't use my MockingSchemaContext, and I don't think the stack overflow is in my code. (Curiously though I can't get a helpful stack trace at the moment, although that might be ReSharper's influence.)

    So, have I missed anything obvious? Any ideas on how I can make it work?

    Many thanks,

    Geoff
    Monday, November 30, 2009 10:43 AM
  • Hi there,

    I had a look at ViewStateCleaningWriter (it is indeed in the beta 2 samples). It looks like another way of intercepting the XAML - to - object creation process, similar to the XamlSchemaContext described above. Do you still think it's worth trying this, or do you reckon it would have the same stack overflow problem I'm experiencing with my custom SchemaContext? (The Xaml reader approach didn't seems as easy or code-light as the schema context one, so I implemented the schema context one first.)

    Many thanks,

        Geoff
    Monday, November 30, 2009 10:48 AM
  • Hi Geoff,
    It's a little hard to be sure,  but since the Xaml Rewriting approach just manipulates XML I think you should be (at least as) safe with that one. I'd be kind of interested to follow up the stack overflow thing further in case it's a bug on our side.
    Tim
    Monday, November 30, 2009 7:18 PM
  • I'd be kind of interested to follow up the stack overflow thing further in case it's a bug on our side.
    Well, I've put together a demonstration project that reproduces the problem. I've kept it as simple as I can - there's one sequence, one real code activity, one derived code activity to mock it, the mocking schema context and the code to run the sequence. It gives a stack overflow very quickly (and still not much of a stack trace, curiously - maybe it's after a managed to native transition?)

    The project code is 12k and is downloadable from:
        http://www.opinionatedgeek.com/stuff/XamlMocking.zip

    Hope this helps,

        Geoff
    Tuesday, December 1, 2009 8:53 AM
  • Have you tried putting a breakpoint in your GetXamlType method to make sure you aren't somehow getting into an infinite loop there?  Not sure why calling the base would invoke your method again, but it's about the only thing I can see in your code that would cause an overflow like that.

    Matt

    Free Guest Pass – Experience .NET training the way it should be.
    Tuesday, December 1, 2009 12:45 PM
  • Sorry, yes, I should have mentioned that.  When I set a breakpoint in GetXamlType, it intercepts the type 3 times and returns properly from the call to base.GetXamlType() each time. (In fact, it goes into my GetXamlType around 40 times in that test/demo project, and returns each time.)

    Geoff
    Tuesday, December 1, 2009 12:55 PM
  • Your overflow appears to be because MockCodeActivity1 inherits from CodeActivity1 instead of from say CodeActivity.  I guess when it tries to resolve MockCodeActivity1, it will try to resolve CodeActivity1, which causes it to resolve MockCodeActivity1 again, ad infinitum.

    I'll try to find out whether this is expected behavior or not.
    Tuesday, December 1, 2009 1:12 PM
  • Interesting! I made sure the mock derived from the proper activity because I thought something would complain if I didn't. I never figured it would complain because of that!

    I'd be really grateful if you could tell me what the expected behaviour is here. To an extent it doesn't really matter to me whether they must derive from the activity they're replacing, or whether they must not derive from the activity they're replacing, as long as I'm sure which one I'm supposed to do.

    I'll go with 'must not' for now. Thanks for investigating this.

    Geoff
    Tuesday, December 1, 2009 1:54 PM
  • This is expected. There are a number of times in XAML parsing where we need to look up a type's base type. If it returns itself as its base, then that will produce either an infinite loop or a stack overflow.

    Whether the mocking type needs to be derived from the mocked type depends on how you're using it. If you're just doing something like:

    <Sequence ...
       <MyActivity ...

    Then the only requirement is that MyActivity be derived from Activity (since Sequence's content property is a collection of Activity).

    If you do actually need to derive from the mocked class, then you can work around this by overriding XamlType.LookupBaseType, and returning the actual mocked type.
    Tuesday, December 1, 2009 6:50 PM
  • That's excellent info. Many thanks.

        Geoff
    Tuesday, December 1, 2009 6:52 PM