Xunit: Inherited traits overriden by class

Created on 9 Aug 2017  路  3Comments  路  Source: xunit/xunit

When assigning a trait to a parent class, it is overriden by the childs class trait instead of added to.

[Trait("Category", "Integration")]
public class TraitExample { }

[Trait("Category", "Test")]
public class InheritedTraitExample : TraitExample
{
    [Fact]
    public void TestExample()
    {
        Assert.True(true);
    }
}

However this makes TestExample marked as integration:

[Trait("Category", "Integration")]
public class TraitExample { }

public class InheritedTraitExample : TraitExample
{
    [Fact]
    [Trait("Category", "Test")]
    public void TestExample()
    {
        Assert.True(true);
    }
}

Adding traits to the test itself doesn't override, but adding any traits to the class does.
not sure if this is a runner (visual studio) problem or xunit itself.

help wanted Bug

Most helpful comment

Well, I've figured out _why_ this is happening, but I'm not sure how best to fix it.

The issue is caused due to the fact that Xunit looks for traits that inherit from the ITraitAttribute interface. In ReflectionAttributeInfo.GetCustomAttributes(Type, Type, AttributeUsageAttribute) we collect all attributes on the type that are assignable to ITraitAttribute and, if the attribute is inheritable and either allows multiple or else hasn't been found yet, we continue with the base type of the current type.

The problem is, Xunit.Sdk.ITraitAttribute isn't actually an attribute itself, so it can't have its own AttributeUsageAttribute, so a default is used. By default AttributeUsageAttribute does not allow multiples, which is why we're seeing this behaviour - and I don't think changing the defaults is a great answer.
I'll submit a quick PR with a fix, although it will need a thorough review, even if it's a small change.

All 3 comments

I attempted reproducing this in a test, but as far as i can see the test method only gets the Traits directly set on it.
The flowing test fails with the traits collection containing only the trait set onto the test method itself, namely "derivedClassTestValue".

[Trait("traitName", "baseClassValue")
class ClassUnderTest { }

class DerivedClassUnderTest :ClassUnderTest
{
        [Fact]
        [Trait("traitName", "derivedClassTestValue")]
        public void TestExample()
        {
        }
}


[Fact]
public void AddsToTraitsFromBaseClassInMethodOfDerivedClass()
{
        var method = typeof(DerivedClassUnderTest).GetMethod("TestExample");

        var traits = TraitHelper.GetTraits(method);

        Assert.Collection(traits.Select(kvp => $"{kvp.Key} = {kvp.Value}").OrderBy(_ => _, StringComparer.OrdinalIgnoreCase),
            value => Assert.Equal("traitName = baseClassValue", value),
            value => Assert.Equal("traitName = derivedClassTestValue", value)
        );

 }

I'm running into this same issue.

My tests happily pick up traits from the first class in the hierarchy that has some, but stops there and doesn't look further. This isn't great for my scenario, as I am using levels of inheritance and would like to add traits at each level have have them _all_ picked up by the actual test.

Interestingly, TraitHelper seems to only be used in Xunit's own unit tests, not the runner itself.

Well, I've figured out _why_ this is happening, but I'm not sure how best to fix it.

The issue is caused due to the fact that Xunit looks for traits that inherit from the ITraitAttribute interface. In ReflectionAttributeInfo.GetCustomAttributes(Type, Type, AttributeUsageAttribute) we collect all attributes on the type that are assignable to ITraitAttribute and, if the attribute is inheritable and either allows multiple or else hasn't been found yet, we continue with the base type of the current type.

The problem is, Xunit.Sdk.ITraitAttribute isn't actually an attribute itself, so it can't have its own AttributeUsageAttribute, so a default is used. By default AttributeUsageAttribute does not allow multiples, which is why we're seeing this behaviour - and I don't think changing the defaults is a great answer.
I'll submit a quick PR with a fix, although it will need a thorough review, even if it's a small change.

Was this page helpful?
0 / 5 - 0 ratings