Click here to Skip to main content
15,885,309 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have an application using MEF and am trying to write integration tests for the Plug-ins.
The code I have is:-

        <pre lang="c#">
        public string Run()
        {
            IFunction IUT = null; string Wanted = "NewUser";
            foreach (Lazy<IFunction, IFunctionData> func in functions)
            {
                if (func.Metadata.FunctionName == Wanted) IUT = func.Value;
            }
            bool res = false;
            res = TestOne(IUT);
            if (res) TestTwo();
            if (res) return "Pass -  NewUser Test";
            return "Fail -  NewUser Test";
        }
        // Normal New User OK
        private bool TestOne(IFunction IUT)
        {
            bool res = false;
            // Set up the VM properties
            var VM = IUT.GetType();
            PropertyInfo prop=VM.GetProperty("UserName");prop.SetValue(prop, "Smith");
            MethodInfo Method = VM.GetMethod("CreateNewUser");
            //...
            //...
            return res;
        }

The IFunction parts are all ViewModels and the Run method finds the correct one OK.
What I want to do is set the UserName property of this VM and then run the Create method

I am stuck on a way to set the VM properties and run the method, what I have does not work.

What I have tried:

Google searches came up with Unit test suggestions but I need to test integration with the rest of the application
Posted
Updated 10-Jul-18 15:56pm
Comments
johannesnestler 11-Jul-18 7:06am    
It should work by reflection as you showed - so what's your exact Problem with that (it's quite common to set private fields for Tests by reflection).
It maybe the problem that your test doesn't know about your concrete vm-Type?
JohnMeers 11-Jul-18 17:10pm    
That's right The test only knows it is a plugin and of type NewUser, I cannot find a way to cast to the actual concrete VM

1 solution

You don't need to use reflection with MEF, you simply Export & Import to share data - the data can exist in a DLL/EXE outside of the main app. So long as MEF can compose, the data will be visible. This is what we did in another MEF Solution[^] where reflection was requested for a plugin system and the plugins automatically load when the DLL/EXE is dropped into the main app's folder.

So, the answer is to create a class to hold your user details in a seperate class (repository), define the Export with a PartCreationPolicy(CreationPolicy.Shared). This will create a static/shared object. Here is an example:
C#
public interface IUserRepository
{
    string UserName { get; set; }
}

[Export(typeof(IUserRepository)),
 PartCreationPolicy(CreationPolicy.Shared)]
public class UserRepository : ObservableBase, IUserRepository
{
    private string userName = "**Not Set**";

    public string UserName
    {
        get => userName;
        set => Set(ref userName, value);
    }
}

NOTE: The above code is not thread safe. I'll leave that up to you to implement if you require that.

Here is the ObservableBase class:
C#
public abstract class ObservableBase : INotifyPropertyChanged
{
    public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<TValue>.Default.Equals(field, default(TValue)) || !field.Equals(newValue))
        {
            field = newValue;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public bool IsInDesignMode => DesignerProperties.GetIsInDesignMode(new DependencyObject());
}

Now you import the UserRepository:
C#
[Import(typeof(IUserRepository), RequiredCreationPolicy = CreationPolicy.Shared)]
public IUserRepository Repo { get; set; }

... and the user data is now shared whereever the above Import is used:
C#
Repo.UserName = "MainUser";

No reflection coding by you is required as it is all handled by MEF. Enjoy!

PS: Yes, MEF is using reflection so you don't have to! ;)
 
Share this answer
 
Comments
JohnMeers 11-Jul-18 6:35am    
I understand that, but I am using ImportMany as I have several plugins which are all IFunctions. Metadata lets me find the right one but it is still an IFunction and only the general IFunction methods etc are visible not the ones for the specific NewUser function. Without referencing NewUser (which would negate the purpose of using MEF) I cannot cast the IFunction to a NewUserViewModel although it IS one.

I have given up and implemented a general IFunction method 'Test(object[])' to set the properties. VERY inelegant so If anyone has a better way it would be welcome
Graeme_Grant 11-Jul-18 6:59am    
What I have proposed shares the user data. So, if you want to execute a method against the user data, you don't have to know which view model as the UserRepository holds the master reference, so you call the method on the shared UserRepository instead.
JohnMeers 11-Jul-18 17:07pm    
Thanks, I didn't fully appreciate your answer, it is significantly different from what I have tried. Have to think about it. If I understand it properly it separates the data from the VM and centralises it, which sounds as if it would be a good MVVM move, but will affect more than Integration testing.
Graeme_Grant 11-Jul-18 17:59pm    
Separation of concerns - ie: removing the data layer from your business layer logic.

What it does also allow for is swapping out the Repository and implement Mock testing or changing your data layer as well which his your Integration testing.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900