Closed lonix1 closed 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.
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.
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.
Version Information
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:
Faker.Internet.Password()
- the extensionPassword.CreateRandom()
- internally callsFaker.Internet.Password()
User.CreateRandom()
- internally callsPassword.CreateRandom()
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:
Faker.Internet.Password()
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.