0

I have created a custom xUnit theory test DataAttribute named RoleAttribute:

public class RoleAttribute : DataAttribute
{
    public Role Role { get; set; }
    public RoleAttribute(Role role, Action<Role> method)
    {
        Role = role;
        AuthRepository.Login(role);
        method(role);
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        return new[] { new object[] { Role } };
    }
}

And I have the test method OpenProfilePageTest:

public class ProfileTest : AuthTest
{
    [Theory, Priority(0)]
    [Role(Enums.Role.SuperUser, OpenProfilePageTest)]
    [Role(Enums.Role.Editor, OpenProfilePageTest)]
    public void OpenProfilePageTest(Enums.Role role)
    {
        var profile = GetPage<ProfilePage>();
        profile.GoTo();
        profile.IsAt();
    }
}

What I want is that for each role (attribute) it executes first:

AuthRepository.Login(role); (constructor of RoleAttribute)

and then resumes with the code inside OpenProfilePageTest() method. Before it repeats the same but for the second attribute.

How can I accomplish this, right now I'm trying to accomplish this by passing the OpenProfilePageTest() method inside the attribute and execute it in its constructor. There must be a better way to accomplish this than passing around the method I believe?

2 Answers2

2

You can achieve this without passing the method, you need to modify your attribute slightly. I changed the attribute to take all the roles you want to test and return them in the data. Here is an example

public class RolesAttribute : DataAttribute
{
    private Role[] _roles;
    public RolesAttribute(params Role[] roles)
    {
        _roles = roles;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        var data = new List<object[]>();
        //We need to add each role param to the list of object[] params
        //This will call the method for each role
        foreach(var role in _roles)
            data.Add(new object[]{role});
        return data;
    }
}

Then in your test, you just pass all the roles you want to test in a single attribute like so

public class ProfileTest : AuthTest
{
    [Theory, Priority(0)]
    [Roles(Enums.Role.SuperUser, Enums.Role.Editor)]
    public void OpenProfilePageTest(Enums.Role role)
    {
        AuthRepository.Login(role);
        var profile = GetPage<ProfilePage>();
        profile.GoTo();
        profile.IsAt();
    }
}
hawkstrider
  • 4,141
  • 16
  • 27
  • Is it possible to accomplish the same by having the `AuthRepository.Login(role);` method executed inside the constructor of `RoleAttribute`? Because right now I would need to add `AuthRepository.Login(role);` in each TestMethod that would use `[Role(Enum.Role)]`. –  Oct 30 '19 at 15:27
  • No because the attribute dictates the calls to the test method. The method gets the param. I makes much more sense to do that in the test itself because that is the context of the single role. – hawkstrider Oct 30 '19 at 15:30
2

Having an Attribute performing functions other than providing meta data about its adorned member is mixing concerns that cause unnecessary complications and not what it was designed for.

The entire custom attribute can be done away with and the built-in data attributes used instead

For example

public class ProfileTest : AuthTest {
    [Theory, Priority(0)]
    [InlineData(Enums.Role.SuperUser)]
    [InlineData(Enums.Role.Editor)]
    public void OpenProfilePageTest(Enums.Role role) {
        //Arrange
        AuthRepository.Login(role);            
        var profile = GetPage<ProfilePage>();

        //Act
        profile.GoTo();

        //Assert
        profile.IsAt();
    }
}

AuthRepository.Login in this case is part of the setup/arrangement for exercising the desired use case.

Nkosi
  • 235,767
  • 35
  • 427
  • 472