1

I want to be able to run the same test with multiple implementations of a dependency.

The normal way to do what I want is the following:

[Theory]
[InlineData(DoerType.Version1)]
[InlineData(DoerType.Version2)]
public void DoSomethingTest(DoerType type)
{
    IDoSomething doer = Factory.Create(type);

    string result = doer.DoSomething();
}

Here's an alternate impl where I've shifted the call to the Factory Method out into a custom attribute:

[Theory]
[DoerVersions(DoerType.Version1, DoerType.Version2)]
public void DoSomethingTest(IDoSomething doer)
{
    string result = doer.DoSomething();
}

public class DoerVersionsAttribute : DataAttribute
{
    private readonly DoerType[] doers;

    public DoerVersionsAttribute(params DoerType[] doers)
    {
        this.doers = doers;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        return doers.Select(d => new object[] { Factory.Create(d) });
    }
}

But given the large amount of tests that I want to do with this, I'm looking for a clean and as-DRY-as-possible way to do it.

Is there a way to avoid the parameter altogether and, for example, set the implementation on the test class field (or its base class)?

public class Tests
{
    private IDoSomething Doer;

    [Theory]
    [DoerVersions(DoerType.Version1,DoerType.Version2)]
    public void DoSomethingTest()
    {
       string result = Doer.DoSomething();
    }
}

Other ways of expressing the same intent could be acceptable too.

Edit: why do I want this?

I didn't include this in the original question to avoid discussions about my motives.

I want to be able to write tests with as little technical information as possible and then run them through different "interfaces". For example (this is still an approximation):

[Theory] 
[Interfaces(Interface.WebChrome, Interface.WebFirefox, Interface.Android)]
public void ShouldBeAbleToSearchBook()
{
    dsl.Users.Login("user1");
    dsl.Searcher.Search("title:BDD");
    dsl.Searcher.ShouldHaveResult("Some BDD book");
}

So, having parameters and factories on each test would add considerable noise to the information ratio.

Francesc Castells
  • 2,692
  • 21
  • 25
  • Like this? https://stackoverflow.com/questions/22093843/pass-complex-parameters-to-theory to provide the implementation as an argument. – Jeremy Lakeman Jan 25 '23 at 00:39
  • @JeremyLakeman no, I already do that in my second versions, with the custom DoerVersionsAttribute. My next iteration would be to pass the implementation as a field of the class (or base class) so I can avoid the argument altogether. For example, with something that intercepted the parameters about to pass to the test, find a field with those types and set them. This will be used with well-known types so this part should be easy. – Francesc Castells Jan 25 '23 at 06:38
  • I did downvote - I understand your question, the attribute, and the fact that you are not taking the point. I have removed the downvote because I have no desire to cause you annoyance but the bottom line is that I think it's simply a bad idea. And if the desire is to save a line of code at the start of the Theory to call the Factory, don't do that, make the code clear and explicit instead. – Ruben Bartelink Jan 25 '23 at 11:03
  • Edited + removed downvote. TL;DR IMO The Factory Method call is perfectly fine, and trying to move it out is not a good move unless there's some other significant factor you are not surfacing in your question: Tests impls should optimise for legibility for someone landing randomly in a test, not saving lines of code. To make the version lists DRY, use `MemberData` to encapsulate lists of `InlineData` you'd otherwise be repeating. – Ruben Bartelink Jan 25 '23 at 11:11
  • @RubenBartelink you are basically assuming that I don't have a good reason for doing that. Which I guess is fine, but you could have just asked for more context or why I want to do it. In any case, I don't see how using MemberData could help. If it is to avoid repeating the list of Version1,Version2 (in my example), that's not necessarily what I want. These are part of the definition of the tests. I just want to get rid of boilerplate that don't add anything to the reader. I'm going to edit the question to add a more specific example. – Francesc Castells Jan 25 '23 at 11:26
  • I'd have to agree with @RubenBartelink, (although I would use ClassData) and have it directly return the ``doer``, then you could avoid the factory method call. You could also roll your own ``MemberData``-like class deriving from ``DataAttribute``, then you could do it with a single line attribute. My answer to https://stackoverflow.com/questions/74727807/xunit-generalized-tests/74745330#74745330 shows how that works (although for a different application). – Christopher Hamkins Feb 01 '23 at 17:03
  • @ChristopherHamkins sorry if I sound dense, but I don't understand what you mean. You say "like class deriving from DataAttribute", which is what I do already in my `DoerVersionsAttribute`. I think that having a custom attribute with that enum parameter is a lot nicer than using the xUnit generic attributes where each test could have different parameters because that is not my use case. I'll have hundreds of tests and they will all use the same "doer". I just want to limit the plumbing that testers have to do for each test. My attribute is good enough. I was exploring making it even cleaner. – Francesc Castells Feb 01 '23 at 17:47
  • OK, you're really after eliminating the method parameter. My suggestion wouldn't have done that. The only other suggestion I have would be to implement all the tests in an abstract class which has a ``Doer`` property that returns a new object on each get. Then make derived classes for each Doer type that only overload the ``Doer`` property. All the tests in each of the derived classes will run with the specific Doer type and you won't need to decorate each test. It's just a bit harder to modify which Doer types you have. – Christopher Hamkins Feb 02 '23 at 16:34
  • @ChristopherHamkins thanks for the suggestion. I'll give it some thought. I do want to decorate each test because the tester will decide which interfaces (see my last example) the test is applicable to and I want xUnt to run the test for each interface. Another thing would be if we can make it so that all the tests in a single test file are applicable to the same interfaces, then yeah, I could maybe play with base classes. Anyway, I think that my custom attribute still solves the problem nicely, with the issue of having to pass the parameter, which we can live with. – Francesc Castells Feb 02 '23 at 20:27

0 Answers0