0

I'm writing tests for my discord bot (using XUnit) and I want to know whether it's possible to replace my tests with one test. If so, how would I do that?

So far I wrote 4 unit tests for my Unity class (which is a wrapper for Unity Container, DI framework). These tests work as expected, but adding a new test everytime I add a new type to container just doesn't seem right. I looked at answers on similar questions, but solutions were either complex or not useful for my case.

My method from Unity class that is being tested:

public static T Resolve<T>()
{
    return Container.Resolve<T>();
}

It returns an instance of corresponding type from Unity Container.

Tests:

[Fact]
public void ResolveIDataStorage_ShouldWork()
{
    var storage1 = Unity.Resolve<IDataStorage>();
    var storage2 = Unity.Resolve<IDataStorage>();

    Assert.NotNull(storage1);
    Assert.NotNull(storage2);
    Assert.Same(storage1, storage2);
}

[Fact]
public void ResolveILogger_ShouldWork()
{
    var logger1 = Unity.Resolve<ILogger>();
    var logger2 = Unity.Resolve<ILogger>();

    Assert.NotNull(logger1);
    Assert.NotNull(logger2);
    Assert.Same(logger1, logger2);
}

[Fact]
public void ResolveDiscordSocketClient_ShouldWork()
{
    var client1 = Unity.Resolve<DiscordSocketClient>();
    var client2 = Unity.Resolve<DiscordSocketClient>();

    Assert.NotNull(client1);
    Assert.NotNull(client2);
    Assert.Same(client1, client2);
}

[Fact]
public void ResolveConnection_ShouldWork()
{
    var con1 = Unity.Resolve<Connection>();
    var con2 = Unity.Resolve<Connection>();

    Assert.NotNull(con1);
    Assert.NotNull(con2);
    Assert.Same(con1, con2);
}

In each test I resolve some type and assert that two objects are not null and that they should be the same instance. Basically, these asserts should work for any type (or for a set of certain types, that could be parameters for [Theory] test), so, to avoid copy-pasting, it would be very convinient to have one test.

Johnny
  • 8,939
  • 2
  • 28
  • 33
Glitch
  • 42
  • 9
  • You can use reflection to make generic of the desired member and invoke it. From there assert the expected behavior. – Nkosi Apr 29 '19 at 01:38
  • Maybe parameterized tests like [this](https://stackoverflow.com/questions/9110419/test-parameterization-in-xunit-net-similar-to-nunit)? – Ray Tayek Apr 29 '19 at 07:58

2 Answers2

4

If your goal is just to have one test that follows the same pattern for each type that you want to test, you could extract the test into its own generic method and just call that for each type within a single test:

[Fact] 
public void Resolve_ShouldWork() 
{ 
    AssertResolvedTypesAreSame<IDataStorage>();
    AssertResolvedTypesAreSame<ILogger>();
    AssertResolvedTypesAreSame<DiscordSocketClient>();
    AssertResolvedTypesAreSame<Connection>();
}

private void AssertResolvedTypesAreSame<T>()
{
    var t1 = Unity.Resolve<T>(); 
    var t2 = Unity.Resolve<T>();

    Assert.NotNull(t1); 
    Assert.NotNull(t2); 
    Assert.Same(t1, t2); 
}
devNull
  • 3,849
  • 1
  • 16
  • 16
  • Turns out it was that easy, thanks. Btw could I transform this test to `[Theory]` test somehow? – Glitch Apr 29 '19 at 12:58
  • @Glitch To my knowledge, for theory you wont be able to do generics. I suggested an options that uses reflection. – Nkosi Apr 29 '19 at 17:26
  • Yeah, it doesn't appear that XUnit offers anything for generic theories (there was a discussion in the GitHub repo [here](https://github.com/xunit/xunit/issues/1378)). The only options appear to include reflection, as @Nkosi mentioned. There's also an example [here](https://stackoverflow.com/a/45178208/5803406) of creating a custom attribute for handling generic parameters – devNull Apr 29 '19 at 17:38
2

Use reflection to make a generic method of the desired type to be resolved and invoke it.

From there assert the expected behavior.

Using inline data you can now repeat the test without duplicating code.

The following example is based on the already provided code.

[Theory]
[InlineData(typeof(IDataStorage))]
[InlineData(typeof(ILogger))]
[InlineData(typeof(DiscordSocketClient))]
[InlineData(typeof(Connection))]
public void Resolve_Singleton_Services_ShouldWork(Type type) {
    //Arrange
    var unityType = typeof(Unity);
    var resolve = unityType.GetMethod("Resolve", BindingFlags.Static| BindingFlags.Public);
    var genericResolve = resolve?.MakeGenericMethod(type);

    //Act
    var instance1 = genericResolve?.Invoke(null, null); // Unity.Resolve<type>()
    var instance2 = genericResolve?.Invoke(null, null); // Unity.Resolve<type>()

    //Assert
    Assert.NotNull(instance1);
    Assert.NotNull(instance2);
    Assert.Same(instance1, instance2);
}

The arrange part of the test assumes that what every configuration for the container has already been invoked.

Nkosi
  • 235,767
  • 35
  • 427
  • 472