I have a working set of unit tests which use a custom TestCaseData entity along with the TestCaseSource attribute to utilize it...I'll start with the example which I currently have working...Tests are decorated with:[NUnit.Framework.TestCaseSource(typeof(ServiceTestCases), "AllTestAccounts")]Tests expect - in this case - a single argument...public void ExistsByIDTest ( ITestAccountInfo testAccountInfo )And the value I am returning for that source...public IEnumerable AllTestAccounts { get { // Return the AllAccounts set as a set of TestCaseData via an IEnumerable... foreach (var testAccountInfo in ServiceTestAccounts.GetAllTestAccounts()) { yield return new TestAccountTestCaseDataAdapter ( testAccountInfo ); } } }Which returns this TestCaseData-based entity.../// <see cref="http://www.nunit.org/index.php?p=testCaseSource&r=2.5.9"/> public class TestAccountTestCaseDataAdapter : NUnit.Framework.TestCaseData { /// <summary> /// /// </summary> /// <param name="testAccountInfo"></param> public TestAccountTestCaseDataAdapter ( ITestAccountInfo testAccountInfo ) { // Validate the Arguments... if (testAccountInfo == null) { throw new ArgumentNullException("testAccountInfo"); } // Reference the Arguments... _testAccountInfo = testAccountInfo; } private ITestAccountInfo _testAccountInfo; public ITestAccountInfo TestAccountInfo { get { return _testAccountInfo; } set { _testAccountInfo = value; } } /// <summary> /// The arguments to pass to the Test method. /// </summary> public new object[] Arguments { get { return new object[] { TestAccountInfo }; } }So now I have a scenario where I want to pass more than 1 argument...Tests are now decorated/defined as such:[NUnit.Framework.TestCaseSource(typeof(FetchOperationTestCases), "AllTestCases")] public void FetchByIDTest ( ITestAccountInfo testAccountInfo, FetchOperationTestCase testCase,A new 'test cases' class was defines and exposes a simple property - just like my working example...In this case each test case is another permutation on top of each Test Account permutation...public IEnumerable AllTestCases{get{foreach (var testAccountInfo in ServiceTestAccounts.GetAllTestAccounts()){foreach (var testCase in FetchOperationTestCases){var adapter = new FetchOperationTestCaseDataAdapter(testAccountInfo,testCase);yield return adapter;}}}}As you can see this required a new 'TestCaseData' entity which includes the additional 'test case' information... inheriting/extending the existing custom one...public class FetchOperationTestCaseDataAdapter : TestAccountTestCaseDataAdapter { public FetchOperationTestCaseDataAdapter ( ITestAccountInfo testAccountInfo, FetchOperationTestCase testCase ) : base (testAccountInfo) { // Validate the inputs... if (testCase == null) { throw new ArgumentNullException("testCase"); } // Reference the inputs... _testCase = testCase; } private readonly FetchOperationTestCase _testCase; public FetchOperationTestCase TestCase { get { return _testCase; } } /// <summary> /// The arguments to pass to the Test method. /// </summary> public new object[] Arguments { get { return new object[] { TestAccountInfo, TestCase }; } }When I execute the above, I get the ReSharper/NUnit exception about Runtime Method binding from reflection... In this case it is the 'parameter count mismatch' exception I am getting...
In addition to the above approach, since I had a working version when a single object is passed to the operation, I am attempting the following... I won't be thrilled to have to do this, but it's not a "big deal" to handle so it's a concession I'm willing to make...
[NUnit.Framework.TestCaseSource(typeof(ContactServicesFetchOperationTestCases), "AllTestCases")] public void FetchContactByKeyTest ( ContactServicesFetchOperationTestCaseArguments arguments )
Where the Arguments are simply a class to represent the 2 values...
public class ContactServicesFetchOperationTestCaseArguments { public ITestAccountInfo TestAccountInfo { get; set; } public ContactServicesFetchOperationTestCase TestCase { get; set; } }
And the actual object[] to be passed to the operation is now built as follows...
/// <summary> /// The arguments to pass to the Test method. /// </summary> public new object[] Arguments { get { return new object
[] { new ContactServicesFetchOperationTestCaseArguments() { TestAccountInfo = TestAccountInfo, TestCase = TestCase } }; } }
When I attempt this approach I get the same exception as above - which just amazes me how finnicky this is being:
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)at NUnit.Core.Reflect.InvokeMethod(MethodInfo method, Object fixture, Object[] args)at NUnit.Core.TestMethod.RunTestMethod(TestResult testResult)at NUnit.Core.TestMethod.RunTestCase(TestResult testResult)
Any good means of debugging this somehow? I'm using ReSharper as my unit test runner if that matters or comes into the equation somehow....
I'll look into the 'combinatorial' thing... That one is entirely new to me...
Otherwise, don't get too overly hung up on my naming there... the Adapter is just basically 'adapting' my other data sources (the TestAccountInfo and TestCase entities) into an NUnit TestCaseData inheriting object. From my understanding, I need to build one of those TestCaseData-based objects that is responsible for describing the test case Name / Description and the 'arguments' that will be passed to the operation... So in this case the IEnumerable is returning those TestCaseData-based objects...For a little more info, I have two clases which act as the 'sources'... these are then deriving their contents from an external source (i.e. xml files) and building up some simple POCO entities...
public static class ServiceTestAccounts {
/// <summary> /// Returns the list of 'all' named Test Accounts /// </summary> /// <returns></returns> public static List<ITestAccountInfo> GetAllTestAccounts() { _allTestAccounts = TestAccountInfoFactory.LoadFromXml ( ".\\ServiceTestAccounts.xml" ); return _allTestAccounts; }
public class FetchOperationTestCases
{public IEnumerable AllTestCases { get
{ // Return the AllAccounts set as a set of TestCaseData via an IEnumerable... foreach (var testAccountInfo in ServiceTestAccounts
.GetAllTestAccounts()) { foreach (var testCase in FetchOperationTestCases) { var adapter = new ContactServicesFetchOperationTestCaseDataAdapter
( testAccountInfo, testCase ); yield return adapter; } } } }
Constructing Test Cases
In constructing tests, NUnit uses each item returned by the enumerator as follows:
If it is an object implementing NUnit.Framework.ITestCaseData, its properties are used to provide the test case. In NUnit 2.5, this is done using reflection to allow compatibility with earlier versions that did not implement ITestCaseData.
The following public fields or properties are used:
- Arguments
- An object[] representing the arguments to the method
- Categories
- An IList of categories to be applied to the test case.
- Description
- Sets the description property of the test
- ExpectedException
- Specifies a the Type of an exception that should be thrown by this invocation
- ExpectedExceptionName
- Specifies a the FullName of an exception that should be thrown by this invocation
- Properties
- An IDictionary of properties to be applied to the test case. Note that the values provided must be compatible with PropertiesAttribute. In particular, use of custom types or enums will cause problems.
- Result
- The expected result to be returned from the method, which must have a compatible return type.
- TestName
- Provides a name for the test. If not specified, a name is generated based on the method name and the arguments provided
- Ignored
- If true, the test case is ignored.
- IgnoreReason
- Specifies the reason for ignoring this test case. If set to a non-empty string, then the test is ignored.
If it is an object[], its members are used to provide the arguments for the method, as in this example, which returns arguments from a named static field.
TestCaseData Class
Although any object implementing ITestCaseData may be used to provide extended test case information, NUnit provides the TestCaseData class for this purpose. The following example returns TestCaseData instances from a data source in a separately defined class.
Don't know it helps you: you might also take a gander at Combinatorial: I've found this a very helpful decoration feeding test case values to tests without needing to worry about as much TestCaseData except in a handful of richer cases: like the Combinatorial is HUGE, or requires richer description than simpler primitive types would permit.
private static IEnumerable<TestCaseData> GetMyTestCases()
{
var testCases = new List<TestCaseData>();
//TODO: Fill in the TestCaseData with values matching the types of your Test arguments.
return testCases;
}
[NUnit.Framework.TestCaseSource(typeof(ContactServicesFetchOperationTestCases), ContactServicesFetchOperationTestCases.AllTestCasesSourceName)] public void FetchContactByKeyTest ( ITestAccountInfo testAccountInfo, ContactServicesFetchOperationTestCase testCase )
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)at NUnit.Core.Reflect.InvokeMethod(MethodInfo method, Object fixture, Object[] args)at NUnit.Core.TestMethod.RunTestMethod(TestResult testResult)at NUnit.Core.TestMethod.RunTestCase(TestResult testResult)
[NUnit.Framework.TestCaseSource(typeof(FetchOperationTestCases), FetchOperationTestCases.AllTestCasesSourceName)] public void FetchContactByKeyTest ( ITestAccountInfo testAccountInfo, FetchOperationTestCase testCase )
Point to this member on the given Type for the test case 'source'...
public virtual IEnumerable AllTestCases { get
{
// Return the AllAccounts set as a set of TestCaseData via an IEnumerable...
foreach (var testAccountInfo in ServiceTestAccounts.GetAllTestAccounts())
{
foreach (var testCase in FetchOperationTestCases)
{
var adapter = new FetchOperationTestCaseDataAdapter( testAccountInfo, testCase ); yield return adapter; } } } }
Which in turn uses this lazy-loaded property when building up the permutations...protected List<FetchOperationTestCase> FetchOperationTestCases { get { if (_fetchOperationTestCases == null) { return _fetchOperationTestCases = LoadFetchOperationScenarios(); } return _fetchOperationTestCases; } }The 'loading' of the XML file... Which is where the exception was being thrown... and ultimately 'swallowed' by the mechanisms involved in executing the tests...protected virtual List<ContactServicesFetchOperationTestCase> LoadFetchOperationScenarios() { try { // Ensure the XML file exists... if (!System.IO.File.Exists(ContactServicesTestCasesFilePath)) { throw new TestCaseSourceException ( String.Format ( "Unable to locate the testing document [{0}]", ContactServicesTestCasesFilePath ) ); }So in this case - here's what I would have expected..... Go ahead - call me crazy! You won't be the first or last! ;-)
- The 'runner' would have encountered that exception when attempting to access the 'source' property that returns an IEnumerable.
- The 'runner' therefore wouldn't have even attempted to invoke the test operation via reflection... since it caught an exception while preparing the 'test data' from the stated source...
The 'runner' would then have thrown its own exception (hopefuly something typed that would convey it's area of functionality), nesting my exception that it caught as the InnerException...Instead we see a call stack that is complaining about the method signature mis-match... Which sent me on a goose-chase looking at why my Argument's propertythat returns the object[] was incorrect - though in reality it was just fine...If I had seen my Exception that stated - hey! dummy! you're file isn't found! - then I would have zero'd in on it right away...Hopefully that helps illustrate what 'went wrong' in my case... and what could be done to improve it for the next guy...The embarrasing part is this - I think I identified this same behavior back when I was getting my 1st go at using TestCaseSource working for just the ITestAccountInfo based operations...I just forgot all about it... it's not exactly intuitive that the real 'root cause' has nothing to do with the call stack we're seeing here...Again - thanks for the help here!