Reputation: 9011
Is there any way to pass generic types using a TestCase to a test in NUnit?
This is what I would like to do but the syntax is not correct...
[Test]
[TestCase<IMyInterface, MyConcreteClass>]
public void MyMethod_GenericCall_MakesGenericCall<TInterface, TConcreteClass>()
{
// Arrange
// Act
var response = MyClassUnderTest.MyMethod<TInterface>();
// Assert
Assert.IsInstanceOf<TConcreteClass>(response);
}
Or if not, what is the best way to achieve the same functionality (obviously I'll have multiple TestCases in the real code)?
Update with another example...
Here is another example with a single generic type passed...
[Test]
[TestCase<MyClass>("Some response")]
public void MyMethod_GenericCall_MakesGenericCall<T>(string expectedResponse)
{
// Arrange
// Act
var response = MyClassUnderTest.MyMethod<T>();
// Assert
Assert.AreEqual(expectedResponse, response);
}
Upvotes: 77
Views: 44294
Reputation: 650
Looks like as of NUnit 4.1 you can do this with TypeArgs
:
[Test]
[TestCase(TypeArgs=[typeof(IMyInterface), typeof(MyConcreteClass)])]
public void MyMethod_GenericCall_MakesGenericCall<TInterface, TConcreteClass>()
{
...
}
https://docs.nunit.org/articles/nunit/writing-tests/attributes/testcase.html
TypeArgs specifies the Types to be used when targeting a generic test method. (NUnit 4.1+)
Upvotes: 0
Reputation: 527
Here's one for TestCaseSource
that will convert every source object into generics:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseSourceGenericAttribute : TestCaseSourceAttribute, ITestBuilder
{
public TestCaseSourceGenericAttribute(string sourceName)
: base(sourceName)
{
}
IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
{
//Method converts every test case source parameter into generic type.
if (!method.IsGenericMethodDefinition)
return BuildFrom(method, suite);
var testCaseSourceTestMethods = BuildFrom(method, suite).ToArray();
var genericTestMethods = new List<TestMethod>();
foreach (var testMethod in testCaseSourceTestMethods)
{
var listOfTypes = new List<Type>();
foreach (var argument in testMethod.Arguments)
{
if (argument is Type typeArgument)
listOfTypes.Add(typeArgument);
else if (argument != null)
listOfTypes.Add(argument.GetType());
}
var genericMethod = testMethod.Method.MakeGenericMethod(listOfTypes.ToArray());
var genericTestMethod = new NUnitTestCaseBuilder().BuildTestMethod(genericMethod, suite, new TestCaseParameters(testMethod.Arguments));
genericTestMethods.Add(genericTestMethod);
}
return genericTestMethods;
}
}
Usage:
public static object[] ConsumersWithMessages =
{new object[] {typeof(CreateEspBusinessAccountConsumer), new CreatedBusinessAccountEvent("ACC1", Guid.NewGuid())}};
[TestCaseSourceGeneric(nameof(ConsumersWithMessages))]
public async Task TestAllConsumers<TConsumer, TMessage>(Type consumerType, object message) where TMessage : class where TConsumer : class, IConsumer
{
var harness = CreateTestHarness<TConsumer>();
await harness.Start();
await harness.Bus.Publish(message);
Assert.True(await harness.Published.Any<TMessage>());
Assert.True(await harness.Consumed.Any<TMessage>());
var consumerHarness = harness.GetConsumerHarness<TConsumer>();
Assert.That(await consumerHarness.Consumed.Any<TMessage>());
}
Upvotes: 0
Reputation: 4004
I slightly modified the TestCaseGenericAttribute somebody posted here:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class GenericTestCaseAttribute : TestCaseAttribute, ITestBuilder
{
public GenericTestCaseAttribute(params object[] arguments)
: base(arguments)
{
}
IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
{
if (!method.IsGenericMethodDefinition) return base.BuildFrom(method, suite);
var numberOfGenericArguments = method.GetGenericArguments().Length;
var typeArguments = Arguments.Take(numberOfGenericArguments).OfType<Type>().ToArray();
if (typeArguments.Length != numberOfGenericArguments)
{
var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
parms.Properties.Set("_SKIPREASON", $"Arguments should have {typeArguments} type elements");
return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
}
var genMethod = method.MakeGenericMethod(typeArguments);
return new TestCaseAttribute(Arguments.Skip(numberOfGenericArguments).ToArray()).BuildFrom(genMethod, suite);
}
}
This version expects one list of all parameters, starting with the type parameters. Usage:
[Test]
[GenericTestCase(typeof(IMailService), typeof(MailService))]
[GenericTestCase(typeof(ILogger), typeof(Logger))]
public void ValidateResolution<TQuery>(Type type)
{
// arrange
var sut = new AutoFacMapper();
// act
sut.RegisterMappings();
var container = sut.Build();
// assert
var item = sut.Container.Resolve<TQuery>();
Assert.AreEqual(type, item.GetType());
}
Note: this can be improved upon once Genetic Atttributes are introduced to the language. (See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/generic-attributes ) At that point, test frameworks will probably include these attributes themselves.
Upvotes: 4
Reputation: 18033
NUnit test methods actually can be generic as long as the generic type arguments can be inferred from parameters:
[TestCase(42)]
[TestCase("string")]
[TestCase(double.Epsilon)]
public void GenericTest<T>(T instance)
{
Console.WriteLine(instance);
}
If the generic arguments cannot be inferred, the test runner will not have a clue how to resolve type arguments:
[TestCase(42)]
[TestCase("string")]
[TestCase(double.Epsilon)]
public void GenericTest<T>(object instance)
{
Console.WriteLine(instance);
}
But for this case you can implement a custom attribute:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseGenericAttribute : TestCaseAttribute, ITestBuilder
{
public TestCaseGenericAttribute(params object[] arguments)
: base(arguments)
{
}
public Type[] TypeArguments { get; set; }
IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
{
if (!method.IsGenericMethodDefinition)
return base.BuildFrom(method, suite);
if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length)
{
var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
parms.Properties.Set(PropertyNames.SkipReason, $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements");
return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
}
var genMethod = method.MakeGenericMethod(TypeArguments);
return base.BuildFrom(genMethod, suite);
}
}
Usage:
[TestCaseGeneric("Some response", TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }]
public void MyMethod_GenericCall_MakesGenericCall<T1, T2>(string expectedResponse)
{
// whatever
}
And a similar customization for TestCaseSourceAttribute
:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseSourceGenericAttribute : TestCaseSourceAttribute, ITestBuilder
{
public TestCaseSourceGenericAttribute(string sourceName)
: base(sourceName)
{
}
public Type[] TypeArguments { get; set; }
IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
{
if (!method.IsGenericMethodDefinition)
return base.BuildFrom(method, suite);
if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length)
{
var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
parms.Properties.Set(PropertyNames.SkipReason, $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements");
return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
}
var genMethod = method.MakeGenericMethod(TypeArguments);
return base.BuildFrom(genMethod, suite);
}
}
Usage:
[TestCaseSourceGeneric(nameof(mySource)), TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }]
Starting with C# 11.0 you can specify generic attributes. This makes possible to use generic [TestCase<...>]
attributes exactly the same way as the OP wanted:
// Requires C# 11.
// For exactly one type argument. See the base implementation above.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseAttribute<T> : TestCaseGenericAttribute
{
public TestCaseAttribute(params object[] arguments)
: base(arguments) => TypeArguments = new[] { typeof(T) };
}
// For exactly two type arguments. See the base implementation above.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseAttribute<T1, T2> : TestCaseGenericAttribute
{
public TestCaseAttribute(params object[] arguments)
: base(arguments) => TypeArguments = new[] { typeof(T1), typeof(T2) };
}
// You can add more classes to support more type arguments or
// to create specialized [TestCaseSource<...>] attributes the same way.
So finally, this is now supported:
[TestCase<IMyInterface, MyConcreteClass>("Some response")]
public void MyMethod_GenericCall_MakesGenericCall<T1, T2>(string expectedResponse)
{
// whatever
}
Upvotes: 109
Reputation: 860
I had occasion to do something similar today, and wasn't happy with using reflection.
I decided to leverage [TestCaseSource] instead by delegating the test logic as a test context to a generic testing class, pinned on a non-generic interface, and called the interface from individual tests (my real tests have many more methods in the interface, and use AutoFixture to set up the context):
class Sut<T>
{
public string ReverseName()
{
return new string(typeof(T).Name.Reverse().ToArray());
}
}
[TestFixture]
class TestingGenerics
{
public static IEnumerable<ITester> TestCases()
{
yield return new Tester<string> { Expectation = "gnirtS"};
yield return new Tester<int> { Expectation = "23tnI" };
yield return new Tester<List<string>> { Expectation = "1`tsiL" };
}
[TestCaseSource("TestCases")]
public void TestReverse(ITester tester)
{
tester.TestReverse();
}
public interface ITester
{
void TestReverse();
}
public class Tester<T> : ITester
{
private Sut<T> _sut;
public string Expectation { get; set; }
public Tester()
{
_sut=new Sut<T>();
}
public void TestReverse()
{
Assert.AreEqual(Expectation,_sut.ReverseName());
}
}
}
Upvotes: 29
Reputation: 3600
I have written my own TestCaseGenericAttribute
and TestCaseGenericSourceAttribute
.
https://github.com/nunit/nunit/issues/3580
Upvotes: 0
Reputation: 3293
You can make custom GenericTestCaseAttribute
[Test]
[GenericTestCase(typeof(MyClass) ,"Some response", TestName = "Test1")]
[GenericTestCase(typeof(MyClass1) ,"Some response", TestName = "Test2")]
public void MapWithInitTest<T>(string expectedResponse)
{
// Arrange
// Act
var response = MyClassUnderTest.MyMethod<T>();
// Assert
Assert.AreEqual(expectedResponse, response);
}
Here is implementation of GenericTestCaseAttribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class GenericTestCaseAttribute : TestCaseAttribute, ITestBuilder
{
private readonly Type _type;
public GenericTestCaseAttribute(Type type, params object[] arguments) : base(arguments)
{
_type = type;
}
IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
{
if (method.IsGenericMethodDefinition && _type != null)
{
var gm = method.MakeGenericMethod(_type);
return BuildFrom(gm, suite);
}
return BuildFrom(method, suite);
}
}
Upvotes: 15
Reputation: 31
As might be testing with generic functions that return objects?. Example:
public Empleado TestObjetoEmpleado(Empleado objEmpleado)
{
return objEmpleado;
}
Thanks
Upvotes: 3
Reputation: 38778
I did something similar last week. Here's what I ended up with:
internal interface ITestRunner
{
void RunTest(object _param, object _expectedValue);
}
internal class TestRunner<T> : ITestRunner
{
public void RunTest(object _param, T _expectedValue)
{
T result = MakeGenericCall<T>();
Assert.AreEqual(_expectedValue, result);
}
public void RunTest(object _param, object _expectedValue)
{
RunTest(_param, (T)_expectedValue);
}
}
And then the test itself:
[Test]
[TestCase(typeof(int), "my param", 20)]
[TestCase(typeof(double), "my param", 123.456789)]
public void TestParse(Type _type, object _param, object _expectedValue)
{
Type runnerType = typeof(TestRunner<>);
var runner = Activator.CreateInstance(runnerType.MakeGenericType(_type));
((ITestRunner)runner).RunTest(_param, _expectedValue);
}
Upvotes: 8
Reputation: 55195
Attributes in C# cannot be generic, so you won't be able to do things exactly as you'd like. Perhaps the easiest thing would be to put TestCase
attributes onto a helper method which uses reflection to call the real method. Something like this might work (note, untested):
[TestCase(typeof(MyClass), "SomeResponse")]
public void TestWrapper(Type t, string s)
{
typeof(MyClassUnderTest).GetMethod("MyMethod_GenericCall_MakesGenericCall").MakeGenericMethod(t).Invoke(null, new [] { s });
}
Upvotes: 11
Reputation: 3109
Start with the test first--even when testing. What do you want to do? Probably something like this:
[Test]
public void Test_GenericCalls()
{
MyMethod_GenericCall_MakesGenericCall<int>("an int response");
MyMethod_GenericCall_MakesGenericCall<string>("a string response");
:
}
Then you can just make your test a plain old function test. No [Test] marker.
public void MyMethod_GenericCall_MakesGenericCall<T>(string expectedResponse)
{
// Arrange
// Act
var response = MyClassUnderTest.MyMethod<T>();
// Assert
Assert.AreEqual(expectedResponse, response);
}
Upvotes: 8