In integration test used not InMemoryDatabase, but i create test ApiFactory with InMemoryDatabase - c#

I use ApiFactory for integration tests and mock DB. I took this code from the xUnit tests manual and use it in another project, where it work good.
public class ApiFactory: WebApplicationFactory<Program>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return builder.ConfigureServices(services =>
{
services.AddDbContext<DatabaseContext>(options =>
options.UseInMemoryDatabase("TestDB"));
})
.UseStartup<Program>();
}
}
[Trait("Category", "Unit")]
public class UserTests : IClassFixture<ApiFactory>
{
private readonly WebApplicationFactory<Program> _factory;
public UserTests(ApiFactory factory)
{
_factory = factory;
}
[Fact]
public void Test1()
{
using (var scope = _factory.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using var db = scope.ServiceProvider.GetService<DatabaseContext>();
db.UserEntity.Add(New UserEntity(){Name = "Test"});
}
var result = _factory.CreateClient().GetAsync("api/v1/user");
var user = await result.ReadResponceContentAsync<UserEntity>();
Assert.Equal("Test", user.Name);
}
}
In this line
using (var scope = _factory.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
not creating ApiFactory, but filling db.
In this line
var user = await result.ReadResponceContentAsync<UserEntity>();
creating ApiFactory and invoke api, but db in service empty and test is fail.

Related

C# Repositories and Http responses not returning expected data using same dbContext XUnit

I've been trying to implement integration tests with a database per test strategy, but I haven't been able to make it work as needed.
This is the factory class that uses WebApplicationFactory:
public class TestFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>
where TProgram : class where TDbContext : DbContext
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveDbContext<TDbContext>();
services.AddDbContext<TDbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
});
services.EnsureDbCreated<TDbContext>();
});
}
}
This is the TestClass:
public class RolesControllerTest : IDisposable
{
private readonly TestFactory<Program, ADbContext> _factory;
private IServiceScope _scope;
private ADbContext_dbContext;
private readonly HttpClient _client;
private IRoleRepository _rolesRepository;
public RolesControllerTest()
{
_factory = new TestFactory<Program, ADbContext>();
_client = _factory.CreateClient();
_scope = _factory.Services.CreateScope();
var scopedServices = _scope.ServiceProvider;
_dbContext = scopedServices.GetRequiredService<ADbContext>();
_dbContext.Database.EnsureCreated();
}
public void Dispose()
{
_factory.Dispose();
_scope.Dispose();
}
// Tests ...
}
This is the test:
[Fact(DisplayName = "GetAsync returns a list of role models")]
public async Task GetAsync_ReturnsTaskOfRoleModelList()
{
var roleModelInDb = new RoleModel
{
Id = Guid.NewGuid(),
Name = "Role A",
Description = "Role A Description"
};
_rolesRepository = new RoleRepository(_dbContext, TestMapperHelper.GenerateTestMapper());
var roleModel = await _rolesRepository.CreateAsync(roleModelInDb);
var responseData = await _client.GetFromJsonAsync<List<RoleModel>>("/api/roles");
responseData.ShouldNotBeNull();
responseData.ShouldBeOfType<List<RoleModel>>();
responseData.Count.ShouldBe(1);
responseData[0].Id.ShouldBe(roleModel.Id);
responseData[0].Name.ShouldBe(roleModelInDb.Name);
}
The repository returns the expected data: the new roleModel that's been added to the db.
The responseData is a list as expected, but it's empty, so the test fails.
If I try to use a client instead of the repository to create the initial roleModel:
var createdResponse = await _client.PostAsJsonAsync("/api/roles", roleModelInDb);
var createdByClient = await TestResponseHelper.GetResponseContent<RoleModel>(createdResponse);
The createdResponse is a 200 OK Http response, and the role model createdByClient is a valid RoleModel, but the test fails, the list is still empty.
If I use a roleRepository to find the previously created roleModel by Id, the result is null.
If I'm using the same database context for the web factory and repositories, why is this happening?

How to close Resharper test runner after finishing integration test in xUnit Test project (.NET Core)?

i am new to integration tests. I have an xUnit project in my solution which contains one test only.
Here's the definition of my test:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "testoweImie",
LastName = "testoweNazwisko",
MailAddress = "test#test.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
And the BaseIntegrationTest class definition:
public abstract class BaseIntegrationTest
{
private const string TestDatabaseName = "TestDatabase";
protected BaseIntegrationTest()
{
var appFactory = new WebApplicationFactory<Startup>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
RemoveDatabaseContextFromServicesCollectionIfFound<EventStoreContext>(services);
RemoveDatabaseContextFromServicesCollectionIfFound<GrantContext>(services);
services
.AddDbContext<EventStoreContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName))
.AddDbContext<GrantContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName));
});
});
HttpClient = appFactory.CreateClient();
}
protected HttpClient HttpClient { get; }
protected static StringContent SerializeObject(object #object) =>
new StringContent(
JsonConvert.SerializeObject(#object),
Encoding.UTF8,
"application/json");
private static void RemoveDatabaseContextFromServicesCollectionIfFound<T>(IServiceCollection services)
where T : DbContext
{
var descriptor = services.SingleOrDefault(service =>
service.ServiceType == typeof(DbContextOptions<T>));
if (!(descriptor is null))
{
services
.Remove(descriptor);
}
}
}
When i run tests, it takes few seconds, and the test ends successfully. The problem is that Resharper Test Runner still runs, although i've already have collected results. what am i doing wrong here? Do i have to somehow dispose the HttpClient, after performing all tests? If so, how to achieve that? Thanks for any help.
It looks like you're actually booting the application inside the test rather than using the testhost (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1)
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;
public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Notice the IClassFixture stuff.

Moq - Mock DbSet<T>.AddAsync throws no invocations performed

I have a unit test that is basically testing the behaviour of EF Core. The class I am trying to test looks like this:
namespace MusicPortal.Repository.Repository
{
public class ArtistRepository : IArtistRepository
{
private readonly MusicPortalDbContext _context;
public ArtistRepository(MusicPortalDbContext context)
{
_context = context;
}
public async Task<MusicPortalDatabaseResponse<bool>> AddNewArtist(Artist artist)
{
try
{
await _context.Artists.AddAsync(new Artist
{
ArtistType = ArtistTypes.Band,
City = artist.City,
Country = artist.Country,
Genre = artist.Genre,
Name = artist.Name,
ProfileImageUrl = artist.ProfileImageUrl
});
_context.SaveChanges();
return new MusicPortalDatabaseResponse<bool>
{
HasError = false,
Exception = null,
Response = true
};
}
catch (Exception e)
{
return new MusicPortalDatabaseResponse<bool>
{
HasError = true,
Exception = e,
Response = false
};
}
}
}
}
And I have the following Unit Test for it using Moq
namespace MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist
{
[TestFixture]
public class GivenAddingANewArtistToADatabaseFails
{
private Mock<DbSet<Artist>> _mockArtistDbSet;
private Mock<MusicPortalDbContext> _mockContext;
private IArtistRepository _artistRepository;
private MusicPortalDatabaseResponse<bool> _addArtistToDbResponse;
[OneTimeSetUp]
public async Task Setup()
{
_mockArtistDbSet = new Mock<DbSet<Artist>>();
_mockContext = new Mock<MusicPortalDbContext>();
_mockArtistDbSet
.Setup(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()))
.Callback((Artist artist, CancellationToken token) => { })
.ReturnsAsync(It.IsAny<EntityEntry<Artist>>());
_mockContext
.Setup(x => x.SaveChanges())
.Throws(new Exception("Cannot save new Artist to Database"));
_artistRepository = new MusicPortal.Repository.Repository.ArtistRepository(_mockContext.Object);
_addArtistToDbResponse = await _artistRepository.AddNewArtist(It.IsAny<Artist>());
}
[Test]
public void ThenANegativeResultIsReturned() // pass
{
Assert.IsFalse(_addArtistToDbResponse.Response);
Assert.IsTrue(_addArtistToDbResponse.HasError);
Assert.IsInstanceOf<Exception>(_addArtistToDbResponse.Exception);
}
[Test]
public void ThenTheArtistContextAddMethodIsCalledOnce() //fail
{
_mockArtistDbSet.Verify(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public void ThenTheArtistsContextSaveMethodIsNeverCalled() //pass
{
_mockContext.Verify(x => x.SaveChanges(), Times.Never);
}
}
}
The first and last assertion pass but ThenTheArtistContextAddMethodIsCalledOnce() fails due to the following error:
MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist.GivenAddingANewArtistToADatabaseFails.ThenTheArtistContextAddMethodIsCalledOnce
Moq.MockException :
Expected invocation on the mock once, but was 0 times: x => x.AddAsync(It.IsAny(), It.IsAny())
Performed invocations:
Mock:1> (x):
No invocations performed.
at Moq.Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
at Moq.Mock1.Verify[TResult](Expression1 expression, Func`1 times)
at MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist.GivenAddingANewArtistToADatabaseFails.ThenTheArtistContextAddMethodIsCalledOnce() in MusicPortal.Tests\Repository\ArtistRepository\AddNewArtist\GivenAddingANewArtistToADatabaseFails.cs:line 53
I'm understanding that the problem code is c#
_mockArtistDbSet
.Setup(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()))
.Callback((Artist artist, CancellationToken token) => { })
.ReturnsAsync(It.IsAny<EntityEntry<Artist>>());
And I know the problem is most likely due to async issue but I don't know why, or what the actual problem is. Any advice, solutions?
You declare and setup _mockArtistDbSet, but you don't use/attach it to the _mockContext. I think you need to add something like:
_mockContext.Setup(m => m.Artist).Returns(_mockArtistDbSet.Object);
So it looks like EF Core is not so easily tested with async tasks such as SaveChangesAsync and AddAsync. In the end, I followed the MS guide for testing EF core and created a mock context. The only downside being I can only test happy path. Although error paths are tested by the service which consumes the repository, I was hoping for more test coverage on the repository layer.
Anyway, here's the spec
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.Repository;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB;
using NUnit.Framework;
using MockArtistRepository = MusicPortal.Repository.Repository.ArtistRepository;
namespace MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist
{
[TestFixture]
public class GivenANewArtistToInsertIntoTheDb
{
private DbContextOptions<MusicPortalDbContext> _options;
private MusicPortalDatabaseResponse<bool> _mockResponse;
[OneTimeSetUp]
public async Task Setup()
{
_options = new MockDbFactory("MusicPortalDB").Options;
using (var context = new MusicPortalDbContext(_options))
{
var artistRepository = new MockArtistRepository(context);
_mockResponse = await artistRepository.AddNewArtist(MockRepositoryData.Artist);
}
}
[Test]
public void AndThenAPositiveResultIsReturned()
{
Assert.Null(_mockResponse.Exception);
Assert.IsTrue(_mockResponse.Response);
Assert.IsFalse(_mockResponse.HasError);
}
[Test]
public void ThenTheArtistShouldBeSavedWithNoProblem()
{
using (var context = new MusicPortalDbContext(_options))
{
Assert.AreEqual(1, context.Artists.Count());
}
}
}
}
and the Mock Database:
using System;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.DBModels;
namespace MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB
{
public sealed class MockDbFactory
{
public DbContextOptions<MusicPortalDbContext> Options { get; }
public MockDbFactory(string dbName)
{
Options = new DbContextOptionsBuilder<MusicPortalDbContext>()
.UseInMemoryDatabase(dbName)
.Options;
}
public void AddArtistsToContext()
{
using (var context = new MusicPortalDbContext(Options))
{
context.Artists.Add(new Artist
{
City = "Orange County",
Country = "USA",
Events = null,
Genre = "Pop Punk",
Id = Guid.Parse("8a07504b-8152-4d8b-8e21-74bf64322ebc"),
Merchandise = null,
Name = "A Day To Remember",
ArtistType = "Band",
ProfileImageUrl = "https://placehold.it/30x30"
});
context.SaveChanges();
}
}
}
}
I hope this helps anyone looking at the same issue. The lesson learned is don't use Async unless you absolutely have to.

Injecting mocked dbContext inside Service - integration tests

I'm trying to implement integration test so I can test my endpoints using HttpClient only
public async Task TestEndPoint()
{
using (var response = await _client.GetAsync($"/api/car/{id}"))
{
//...
}
}
I'm using Fixture class to help me with this
private readonly TestServer _server;
public Fixture()
{
var dbContextOptions = new DbContextOptionsBuilder<CarDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var mockContext = new Mock<CarDbContext>(dbContextOptions);
var mockOwnerSet = new Mock<DbSet<Owner>>();
var mockCarSet = new Mock<DbSet<Car>>();
mockContext.Setup(m => m.Owners).Returns(mockOwnerSet.Object);
mockContext.Setup(m => m.Cars).Returns(mockCarSet.Object);
var carService = new CarService(mockContext.Object);
_server = new TestServer(new WebHostBuilder()
.ConfigureAppConfiguration((context, conf) =>
{
conf.AddJsonFile(#Directory.GetCurrentDirectory() + "../appsettings.json");
}).UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddDbContext<CarDbContext>(options => options.UseInMemoryDatabase("Test"));
services.AddScoped<ICarService>(_ => carService);
})
);
Client = _server.CreateClient();
}
Inside Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CarDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("Db")); });
services.AddTransient<ICarService, CarService>();
}
}
Service class
public class CarService: ICarService {
private readonly CarDbContext _carDbContext;
public CarService(CarDbContext carDbContext)
{
_carDbContext = carDbContext;
}
public async Task<Car> GetAsync(int id)
{
return await _carDbContext.Cars.FindAsync(id); //this always returns null
}
}
My question is:
Why mock db context is not being used inside CarService
return await _carDbContext.Cars.FindAsync(id);
always returns null
Update:
private readonly TestServer _server;
public TestServer Server;
public Fixture()
{
_server = new TestServer(new WebHostBuilder()
.ConfigureAppConfiguration((context, conf) =>
{
conf.AddJsonFile(#Directory.GetCurrentDirectory() + "../appsettings.json");
}).UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddDbContext<CarDbContext>(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
})
);
Client = _server.CreateClient();
Server = _server;
}
// inside test method
using (var scope = _server.Host.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<CarDbContext>();
db.Database.EnsureDeleted();
Utilities.Seed(db);
}
And Seed data
public static class Utilities
{
public static void Seed(CarDbContext db)
{
db.Cars.Add(new Car("1", "White", "Toyota"));
db.SaveChanges(true);
}
}
What exactly am I doing wrong where?
I'm still getting null when retrieving data in
public async Task<Car> GetAsync(int id)
{
return await _carDbContext.Cars.FindAsync(id); //this always returns null
}
You don't need to mock the context or your service. Remove everything before the _server = new TestServer line. In your web host setup, you've already set the context to use the in-memory provider, so that's all that's necessary. The context will be injected into the service and the service will be injected where it's needed. The only thing that's changing here is the context connection. Instead of hitting something real like a SQL Server instance, it will simply be storing and retrieving from memory instead.
The only other thing you need to do for your test is to seed the "database" with the data you want to test against. In your test, you'll need to access the test server instance and do:
using (var scope = _server.Host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<CarDbContext>();
context.EnsureDeleted();
// seed test data
}
// test code

Asp.net core session unit test

I hava a problem with unit testing asp.net core mvc controller!
the problem is that in my controller, i use sessions:
[HttpPost]
public IActionResult OpretLobby(LobbyViewModel lobby)
{
try
{
//find brugeren der har lavet lobby
var currentUser = HttpContext.Session.GetObjectFromJson<User>("user");
//save as a lobby
ILobby nyLobby = new Lobby(currentUser.Username)
{
Id = lobby.Id
};
SessionExtension.SetObjectAsJson(HttpContext.Session, lobby.Id, nyLobby);
//add to the list
_lobbyList.Add(nyLobby);
return RedirectToAction("Lobby","Lobby",lobby);
}
this all works perfectly well online on the server, nothing wrong here.
BUT when it comes to the demand of unit testing this whole thing, its not so perfect anymore.
basicly the problem is, that i cant get access to my session from a test.. i have tryed in many ways to create mocks and what not, but most of the solutions work for .net framework, and not for .net core for some reason! please help im in pain!
note:
i used a dummy version of a test to isolate this problem:
[Test]
public void TestIsWorking()
{
SessionExtension.SetObjectAsJson(uut.HttpContext.Session, "user", _savedUser);
//ViewResult result = uut.OpretLobby(lobbyViewModel) as ViewResult;
//Assert.AreEqual("OpretLobby", result.ViewName);
Assert.AreEqual(1,1);
}
goes wrong also here trying to set the session for a user :/
It seems that GetObjectFromJson is an extension method. If so, we could not mock static method easily.
I normally create an abstraction for that kind of scenario. Then register it in DI container, and inject the dependency to the controller.
Startup.cs
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUserSession, UserSession>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
...
}
...
}
Abstraction
public class User
{
public string Username { get; set; }
}
public interface IUserSession
{
User User { get; }
}
public class UserSession : IUserSession
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserSession(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public User User => _httpContextAccessor.HttpContext.Session.GetObjectFromJson<User>("user");
}
public static class SessionExtensions
{
public static User GetObjectFromJson<User>(
this ISession sesson, string json) where User : new()
{
return new User(); // Dummy extension method just to test OP's code
}
}
Controller
public class HomeController : Controller
{
private readonly IUserSession _userSession;
public HomeController(IUserSession userSession)
{
_userSession = userSession;
}
public IActionResult OpretLobby()
{
var currentUser = _userSession.User;
return View(currentUser);
}
}
}
Unit Tests
using AspNetCore.Controllers;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Xunit;
namespace XUnitTestProject1
{
public class HomeControllerTests
{
private readonly User _user;
public HomeControllerTests()
{
_user = new User {Username = "johndoe"};
}
[Fact]
public void OpretLobby_Test()
{
// Arrange
var mockUserSession = new Mock<IUserSession>();
mockUserSession.Setup(x => x.User).Returns(_user);
var sut = new HomeController(mockUserSession.Object);
// Act
var result = sut.OpretLobby();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var user = Assert.IsType<User>(viewResult.Model);
Assert.Equal(_user.Username, user.Username);
}
}
}
#nicholas-ladefoged
I would rather recommend you use integration testing when you need to get some content from session
asp.net core has an awesome TestHost nuget package which can help you validate logic using integration way for testing.
Try add a below code snippet
TestServer server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices(services =>
{
services.AddHttpContextAccessor();
}));
var client = server.CreateClient();
var response = await client.GetAsync(""); // I'm using GetAsync just for sample
here you go! You will have a real session that you can test
BTW, Moq library hasn't ability to mock a static methods, so there a lot of issues. I think that will be more easier to use an integration test in your situation
Win solved it! Here is the final test code if anyone is wondering.
// Arrange
var mockUserSession = new Mock<IUserSession>();
mockUserSession.Setup(x => x.User).Returns(_savedUser);
var sut = new LobbyController(FakeSwagCommunication,mockUserSession.Object);
// Act
var result = sut.OpretLobby(_lobbyViewModel);
// Assert
Assert.IsInstanceOf<RedirectToActionResult>(result);

Categories