bchavez / Bogus

:card_index: A simple fake data generator for C#, F#, and VB.NET. Based on and ported from the famed faker.js.
Other
8.66k stars 495 forks source link

[Question] Testing / mocking custom extensions #382

Closed lonix1 closed 3 years ago

lonix1 commented 3 years ago

Version Information

Software Version(s)
Bogus NuGet Package 33.0.2
.NET Core? 5.0.301
.NET Full Framework?
Windows OS?
Linux OS? yes
Visual Studio? VSCode
Locale en

What's the problem?

I created custom faker features, and want to test them. But there aren't virtual members (that I'm aware of) that I can mock.

For example, I created a password generator. Let's say it's used like this:

I can easily test the Faker.Internet.Password() custom extension itself: test for minimum length, max length, upper/lowercase, digits, etc.

But the other methods should be tested too - which leads to identical tests in all three test classes...

Ideally:

But the library doesn't have virtual members that I can tap into / mock.

So: what is the recommended way to do this?

What possible solutions have you considered?

Looked through the library's tests, but couldn't find anything applicable to my use case.

Do you have sample code to show what you're trying to do?

See above.

bchavez commented 3 years ago

Hi. You'll need to provide a more concrete and distinct example with unit tests and example usages in code. I don't have a clear picture of what you're trying to achieve.

lonix1 commented 3 years ago

UPDATE for TL/DR skip to bottom of next comment


Allow me to try again.

A typical extension:

public static class BogusExtensions {

  public static string Password(this Internet internet, int min, int max, bool requireDigit) {
    // ...generate a random password
  }

}

That can be used like this: Faker.Internet.Password(1, 10, true). And it's easy to test.

But suppose we now have this type that encapsulates a password:

public class Password {

  public Password(string password) { /* ... */ }

  private string Value;

  public string GetHash() { /* ... */ }
  public bool Matches(Password other) { /* ... */ }
  public bool IsValid() { /*... */ }
  // other methods...

  public static Password CreateRandom(int min, int max, bool requireDigit) {
    var password = new Faker().Internet.Password(min, max, requireDigit);
    return new Password(password);
  }

}

Notice the CreateRandom() method which calls Bogus. Some client code may need to create a Password based on a fake, maybe for examples or whatever. That method must be tested - but you'll duplicate all the tests you wrote for BogusExtensions.Password.

So you should instead mock the Bogus generator, so that you can test that method in isolation. I'd also write a test that ensures the mock was called. That way I know that this method relies on Bogus, and I've already tested Bogus, so no need to do so again.

Unfortunately there aren't any virtual members anywhere in Bogus, so there's nothing I can mock.

lonix1 commented 3 years ago

Alright I've found a workaround. Here it is for anyone that needs it.

The assumption is that you're trying to mock third-party code that doesn't have virtual members, and everything is static.

public static class BogusExtensions {

  // called as before: `faker.Internet.Password(1, 10, true)`
  // should be tested, e.g. internet==null, min<0, max<min, etc.
  public static string Password(this Internet internet, int min, int max, bool requireDigit) =>
    PasswordGeneratorInstance.Generate(internet, min, max, requireDigit);

  // this is new; `internal` so calling code can't use it, 
  // but tests can (if test assembly specified in `InternalsVisibleTo`)
  internal class PasswordGenerator {

    // notice the `virtual` - this can be mocked
    public virtual string Generate(Internet internet, int min, int max, bool requireDigit) {
      // contains implementation that used to be in `BogusExtensions.Password()`
      // ...
    }

  }

  private static readonly PasswordGenerator _passwordGeneratorInstance = new();

  // can be called by tests to use a mock
  internal static PasswordGenerator PasswordGeneratorInstance { get; set; } = _passwordGeneratorInstance;

  // must be called during test teardown, else future tests will use mock (as this property is `static`)
  internal static void ResetPasswordGenerator() => PasswordGeneratorInstance = _passwordGeneratorInstance;

}

And a test for Password type (from my comment above):

public class PasswordTests : IDisposable {

  public void Dispose() => BogusExtensions.ResetPasswordGenerator();   // MUST DO THIS!

  [Fact]
  public void calls_Bogus_to_generate_random_password() {

    // arrange
    var passwordString = "foo";

    var mockPasswordGenerator = Substitute.For<BogusExtensions.PasswordGenerator>();
    mockPasswordGenerator.Generate(Arg.Any<Internet>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<bool>()).Returns(passwordString);
    BogusExtensions.PasswordGeneratorInstance = mockPasswordGenerator;

    // act
    var password = Password.CreateRandom(1, 10, true);

    // assert
    password.Value.Should().Be(passwordString);
    mockPasswordGenerator.ReceivedWithAnyArgs(1).Generate(default!, default!);
  }

  [Fact]
  public void returns_non_null_instance() { /* ... */ }

  // other tests...

}

(When using XUnit, the mocking code must be run serially rather than in parallel, as it accesses a non thread-safe resources (static properties). Otherwise tests competing for that static would fail.)

Now I can test in isolation, because I'm using a mock. I don't need to test password generation in this method again, because I already tested the Bogus extension, and I know that it's using that Bogus extension (I verified that as the mock ReceivedWithAnyArgs(1)).


This works, but is ugly. Ideally library should have virtual members here and there so that it's easier to test custom code.

If anyone finds a better way, please let me know.