0

I'm using XUnit, FakeItEasy and Dapper. I'm testing a .NET Core 2.1 WebAPI call. When I spin up the actual web site, I have no errors. Something seems to be wrong with my test.

For XUnit I'm using a DatabaseFixture like so:

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        Settings = GetSettings();
        var serviceProvider = CreateServices();

        using (var scope = serviceProvider.CreateScope())
        {
            EnsureDatabase();
            UpdateDatabase(scope.ServiceProvider);
            Db = new SqlConnection(Settings.TestDbConnectionString);
        }
    }
    ...

My web controller looks like this:

    public async Task<ActionResult> Authorization()
    {
        ...
        var user = await _userRepository.FindBySubject(subject);  // <== First call to DB, all good
        if (user == null) return Ok(new { UserNotRegistered = true });

        var roles = await _userRepository.GetRoles(subject); // <== Second call to DB, ConnectionString off of Fake is ""
        ....

        return Ok(roles);
    }

UserRepo looks like this:

public class UserRepository 
{
    ....
    public async Task<Membership> FindByNameAsync(string subject_cn, CancellationToken cancellationToken)
    {
        using (var connection = _connectionFactory.GetConnection())
        {
            return await connection.QuerySingleOrDefaultAsync<Membership>($@"SQL Code", new {subject_cn});
        }
    }

    public async Task<Membership> FindBySubject(string subject_cn)
    {
        return await FindByNameAsync(subject_cn, new CancellationToken());  // First call, all good
    }

    public async Task<IList<string>> GetRoles(string subject_cn)
    {
        var membership = await FindBySubject(subject_cn); //Second call, no connection string :(
        return await GetRolesAsync(membership, new CancellationToken());
    }

And finally and probably most important, my test:

[Collection(nameof(DatabaseFixture))]
public class UserControllerTests
{
    private readonly IDatabaseConnectionFactory _connectionFactory;
    public UserControllerTests(DatabaseFixture fixture)
    {
        _connectionFactory = A.Fake<IDatabaseConnectionFactory>();
        A.CallTo(() => _connectionFactory.GetConnection()).Returns(fixture.Db);
    }

    [Fact]
    public async Task Authorize_ReturnsRoles()
    {
        var configurationManager = A.Fake<IConfigurationManager>();
        A.CallTo(() => configurationManager.GetSettings()).Returns(new Web.Settings() { ... stuff ... });

        var authenticationManager = A.Fake<IAuthenticationManager>();

        var userController = new UserController(new UserRepository(_connectionFactory), authenticationManager, configurationManager);

        var response = await userController.Authorization(); // Connection string is empty on second data call
        var viewResult = Assert.IsType<ViewResult>(response);
        var model = Assert.IsAssignableFrom<AuthorizationResult>(viewResult);

        Assert.Contains("A Role", model.Roles);
        Assert.Equal(AuthorizationResult.UserState.Authenticated, model.Status);
        A.CallTo(() => authenticationManager.AddToRoles(A.Fake<Membership>(), model.Roles)).MustHaveHappened();
        A.CallTo(() => authenticationManager.SignIn(A.Fake<Membership>())).MustHaveHappened();
    }
}

I'm not sure how the fake is "forgetting" the connection string. The specific error being returned is: Message: System.InvalidOperationException : The ConnectionString property has not been initialized. When I debug after the first call, sure enough the connection string on the connection object is blank. Can anyone shed some light on this situation?

rball
  • 6,925
  • 7
  • 49
  • 77
  • Can you check that you're getting the same `SqlConnection` instance in each call? (use the "Make Object Id" debugger feature for this) – Thomas Levesque Oct 12 '18 at 22:12
  • @ThomasLevesque let me look that up and see how to do it and I'll let you know. – rball Oct 12 '18 at 22:59
  • @ThomasLevesque yes it appears that it's the same SqlConnection instance. Just after the first use the ConnectionString value is blank. – rball Oct 12 '18 at 23:19
  • Changed code to A.CallTo(() => _connectionFactory.GetConnection()).Returns(new SqlConnection(settings.TestDbConnectionString)); to try something different, same result. – rball Oct 12 '18 at 23:47
  • Try this: `A.CallTo(() => _connectionFactory.GetConnection()).ReturnsLazily(() => new SqlConnection(settings.TestDbConnectionString))`. This will return a new connection for every call. – Thomas Levesque Oct 13 '18 at 07:45
  • 1
    Anyway, the connection isn't a fake, so I don't think the problem has anything to do with FakeItEasy. – Thomas Levesque Oct 13 '18 at 07:46
  • @ThomasLevesque I don't think it's an issue with FakeItEasy per se but I do think that something someplace is removing the connection string property and I'm guessing it is more with how I am setting up the fake itself. The ReturnsLazily seems to have worked. Want to make an answer? – rball Oct 15 '18 at 17:30

1 Answers1

0

I'm not sure exactly what's going on. I don't think the problem is with FakeItEasy, since the connection itself isn't a fake. As a workaround, what you could do is make the fake connection factory return a new connection for every call:

A.CallTo(() => _connectionFactory.GetConnection())
    .ReturnsLazily(() => new SqlConnection(settings.TestDbConnectionString)).
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Could be either FakeItEasy or XUnit's fixture doing something wonky. `A.CallTo(() => _connectionFactory.GetConnection()).ReturnsLazily(() => fixture.Db);` still doesn't work. – rball Oct 17 '18 at 19:01