2

I have a unit test:

[Test]
[TestCaseSource(typeof(AllExampleEnumValues))]
public void GivenSth_WhenSth_ThenSth(ExampleEnum enumValue)
{
    // Given
    var givenState = BaseClassProperty; // property from fixture's base class
    systemUnderTest.SetSomeState(givenState);

    // When
    var result = systemUnderTest.AnswerAQuestion(
        "What's the answer to"
        + " the Ultimate Question of Life,"
        + " the Universe,"
        + " and Everything?");

    // Then
    Assert.That(result, Is.EqualTo(42));
}

where AllExampleEnumValues is as follows:

public class AllGranularities : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        return Enum
            .GetValues(typeof(ExampleEnum))
            .Cast<ExampleEnum>()
            .Select(ee => new object[] { ee })
            .GetEnumerator();
    }
}

and in the base class I have:

protected int BaseClassProperty
{
    get
    {
        return //depending on enumValue
    }
}

How can I get the current value of enumValue parameter during runtime within the BaseClassProperty, without changing it to a function and passing enumValue as parameter?

I know I could somehow walk the stackframe and read it from there (no, actually, I could not: How can I get the values of the parameters of a calling method?), but perhaps there is a more elegant solution?

I need this to improve tests' readability - the fact that enumValue will influence the result of reading BaseClassProperty implicitly won't reduce readability, because in my domain this dependency is obvious to everyone.

I've been looking into the TextContext class, but it seems it has nothing useful in this context. As mentioned in the comments, the value is present in test's name - however parsing the value from a string is not acceptable here, as sometimes we use multiple parameters, and generic test cases.

Another trail leads to using IApplyToTest attribute. This however would require applying this attribute to every test.

And finally, another trail leads to using ITestAction, from within I have access to the ITest interface, which gives a TestMethod instance. It has the Arguments property! Unfortunately - it's private.

Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • What do you need the value for? Would it be OK to have it as a string, rather than the actual value? – Thomas Levesque Mar 13 '17 at 12:57
  • @ThomasLevesque I know it's present in the test name. However, we are using multiple parameters sometimes and parsing the value from such string would be too complicated and not very safe. Thanks for the suggestion though! – BartoszKP Mar 13 '17 at 12:58
  • 1
    I think your best bet is to request the feature in the NUnit repo. It's probably not too complex to implement. – Thomas Levesque Mar 13 '17 at 13:05
  • I don't think this is possible to answer without indicating how you translate your Given/When/Then methods in NUnit terms. You indicated separately that they are methods of your own design. – Charlie Mar 13 '17 at 19:38
  • @Charlie No, they are completely irrelevant. Since they are causing some confusion, I've removed them from the snippet. They were just clever wrappers to what's in the snippet currently. – BartoszKP Mar 13 '17 at 20:42

1 Answers1

0

I've managed to create the following workaround:

[AttributeUsage(AttributeTargets.Class)]
public class TrackEnumValueActionAttribute : Attribute, ITestAction
{
    public ActionTargets Targets
    {
        get
        {
            return ActionTargets.Test;
        }
    }

    public void AfterTest(ITest test)
    {
    }

    public void BeforeTest(ITest test)
    {
        if (!(test.Fixture is ICurrentExampleEnumValueHolder))
        {
            return;
        }

        var arguments = GetArgumentsFromTestMethodOrNull(test);

        if (arguments == null)
        {
            return;
        }

        var eumValue = GetEnumValueFromArgumentsOrNull<ExampleEnum>(arguments);

        (test.Fixture as ICurrentExampleEnumValueHolder).CurrentEnumValue = enumValue;
    }

    private object[] GetArgumentsFromTestMethodOrNull(ITest test)
    {
        return test
            .GetType()
            .GetProperty("Arguments", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
            ?.GetValue(test) as object[];
    }

    private T? GetEnumValueFromArgumentsOrNull<T>(object[] arguments) where T : struct
    {
        return arguments
            .Where(a => a.GetType().Equals(typeof(T)))
            .Select(a => (T?)a)
            .FirstOrDefault();
    }
}

Applying this attribute to the base class of my fixture, and implementing the ICurrentExampleEnumValueHolder interface does the trick - the value of my property (present in the interface) has a value that depends on test method call arguments.

Unfortunately, the Arguments property of a TestMethod class in NUnit is internal, hence the ugly reflection workaround. I've posted an issue regarding this matter: https://github.com/nunit/nunit/issues/2079

BartoszKP
  • 34,786
  • 15
  • 102
  • 130