In previous versions of MassTransit, when writing unit tests, I used to seed initial state of a saga by calling the InMemorySagaRepository.Add() method:
private InMemorySagaRepository<TState> Repository { get; }
protected async Task SeedSaga<TState>(TState seedState)
where TState : class, SagaStateMachineInstance
{
await Repository.Add(new SagaInstance<TState>(seedState), default);
}
This method would add an entry for the saga in the desired state in the repository, which could be used as a starting point for my tests.
Now I upgraded to the version 7.2.1 of MassTransit and this method is no longer available.
Is it possible to seed data in the current version of the InMemorySagaRepository? If not, what approach could be taken to achive similar results?
Thanks.
There was a discussion on this late last year. In summary, you need to be using the container-based in-memory test harness.
var services = new ServiceCollection();
services.AddMassTransitInMemoryTestHarness(x =>
{
x.AddSagaStateMachineTestHarness<MySagaStateMachine, MySagaState>();
x.AddSagaStateMachine<MySagaStateMachine, MySagaState>()
.InMemoryRepository();
});
var provider = services.BuildServiceProvider();
Get and start the test harness (be sure to stop it in finally):
var harness = provider.GetRequiredService<InMemoryTestHarness>();
await harness.Start();
You can obtain the saga dictionary and add your seeded saga instance:
var dictionary = provider.GetRequiredService<IndexedSagaDictionary<MySagaState>>();
dictionary.Add(new SagaInstance(new MySagaState
{
CorrelationId = ...,
OtherProperty = ...
}));
The linked discussion has some useful extension methods as well.
Related
I'm trying to setup unit testing for my API controllers. I'm using the mediatr pattern and FakeIteasy.
I have the following code.
public class ChannelGroupChannelsControllerTests
{
private readonly ChannelGroupChannelsController _controller;
private readonly IMediator _mediator;
public ChannelGroupChannelsControllerTests()
{
var service = A.Fake<IReadChannelGroupChannel>();
var mapper = A.Fake<IMapper>();
var channelGroupChannel = new ChannelGroupChannel
{
Id = 1,
ChannelGroupId = 1,
ChannelId = 1,
Channel = new Channel { Name = "Channel Name" }
};
_mediator = A.Fake<IMediator>();
_controller = new ChannelGroupChannelsController(_mediator, mapper);
A.CallTo(() => _mediator.Send(A<GetChannelGroupChannelById>._, A<CancellationToken>._)).Returns(channelGroupChannel);
}
[Fact]
public async Task ChannelGroupChannelsController_ById()
{
var result = await _controller.ById(1);
(result.Result as StatusCodeResult)?.StatusCode.Should().Be((int)HttpStatusCode.OK);
result.Value.Should().BeOfType<ChannelGroupChannelVM>();
}
}
Now the problem is that I keep getting NULL as a value.
I think the issue might be that GetChannelGroupChannelById has a constructor that expects the ID. But I'm not sure...
Does anybody know what could be wrong? I'm pretty new with the mocking stuff.
Kind regards
I'm not familiar with mediatr, so may be off base here, and it would be much easier to answer this question if we saw what your controller was doing. If you're able, please supply the code, as without that insight, I'm left to guess a little, but I'll try.
If GetChannelGroupChannelById's constructor expects an ID (an int?), FakeItEasy will provide an ID when it makes the Fake version. If it's an int, FakeItEasy will provide a 0, unless you've done some very fancy configuration you've not shown us. If that's supposed to line up with some other value in your code and doesn't, it may cause your problem.
Otherwise, I see you have a Fake IMapper, that is never configured, but is passed into the controller. I'm guessing this is supposed to translate some values. An unconfigured Fake will return a dummy value (or default if no dummy value can be made). It's possible that this unconfigured mapper is interrupting your flow.
(I also see that service in the test class constructor is unused. I would remove it or use it. It may not be part of your problem, but it's at least distracting.)
I'm putting together an implementation of IdentityServer4, using PostgreSQL as the database, Marten as the ORM, and GraphQL as the API. So far, it's working great at runtime. However, I'm also trying to get unit tests in place, and am running into an issue. I have a custom implementation of IdentityServer4's IClientStore interface, where the implementation of the FindClientByIdAsync method looks thus:
public async Task<Client> FindClientByIdAsync(string clientId)
{
var client = await _documentSession.Query<dbe.Client>().FirstOrDefaultAsync(c => c.ClientId == clientId);
return _mapper.Map<Client>(client); // AutoMapper conversion call
}
This works great at runtime. However, I have the following test that I'm trying to put in place to exorcise this code:
[Fact]
public async Task FindClientByIdReturnsClient()
{
var clients = new []
{
new dbe.Client
{
ClientId = "123"
}
}.AsQueryable();
var queryable = new MartenQueryable<dbe.Client>(clients.Provider);
// _documentSession is a Moq Mock
_documentSession.Setup(x => x.Query<dbe.Client>()).Returns(queryable);
var store = new ClientStore(_documentSession.Object, _mapper);
var result = await store.FindClientByIdAsync("123");
Assert.NotNull(result);
Assert.Equal("123", result.ClientId);
}
The error I get happens when the test tries to execute the FindClientByIdAsync method:
System.InvalidCastException : Unable to cast object of type 'System.Linq.EnumerableQuery`1[StaticSphere.Persona.Data.Entities.Client]' to type 'Marten.Linq.IMartenQueryable'.
If anyone is familiar with Marten can could provide some insight, that would be great! I've done my Google time, and haven't found anything concrete on the subject.
A quote from the creator of Marten which could be relevant here (context):
You can mock a bit of IDocumentSession (Load, Store, SaveChanges,
maybe query by compiled query), but you’re gonna be in a world of hurt
if you try to mock the Linq support.
So one solution would be to do integration tests for which you can find some code from the official Marten's repository or here.
I have a simple hub that I am trying to write a test for with FakeItEasy and the verification of calling the client is not passing. I have the example working in a separate project that uses MOQ and XUnit.
public interface IScheduleHubClientContract
{
void UpdateToCheckedIn(string id);
}
public void UpdateToCheckedIn_Should_Broadcast_Id()
{
var hub = new ScheduleHub();
var clients = A.Fake<IHubCallerConnectionContext<dynamic>>();
var all = A.Fake<IScheduleHubClientContract>();
var id= "123456789";
hub.Clients = clients;
A.CallTo(() => all.UpdateToCheckedIn(A<string>.Ignored)).MustHaveHappened();
A.CallTo(() => clients.All).Returns(all);
hub.UpdateToCheckedIn(id);
}
I'm using Fixie as the Unit Test Framework and it reports:
FakeItEasy.ExpectationException:
Expected to find it once or more but no calls were made to the fake object.
The sample below works in XUnit & MOQ:
public interface IScheduleClientContract
{
void UpdateToCheckedIn(string id);
}
[Fact]
public void UpdateToCheckedIn_Should_Broadcast_Id()
{
var hub = new ScheduleHub();
var clients = new Mock<IHubCallerConnectionContext<dynamic>>();
var all = new Mock<IScheduleClientContract>();
hub.Clients = clients.Object;
all.Setup(m=>m.UpdateToCheckedIn(It.IsAny<string>())).Verifiable();
clients.Setup(m => m.All).Returns(all.Object);
hub.UpdateToCheckedIn("id");
all.VerifyAll();
}
I'm not sure what I've missed in the conversion?
You're doing some steps in a weird (it looks to me, without seeing the innards of your classes) order, and I believe that's the problem.
I think your key problem is that you're attempting to verify that all.UpdateToCheckedIn must have happened before even calling hub.UpdateToCheckedIn. (I don't know for sure that hub.UpdateToCheckedIn calls all.UpdateToCheckedIn, but it sounds reasonable.
There's another problem, where you configure clients.Setup to return all.Object, which happens after you assert the call to all.UpdateToCheckedIn. I'm not sure whether that's necessary or not, but thought I'd mention it.
The usual ordering is
arrange the fakes (and whatever else you need)
act, but exercising the system under test (hub)
assert that expected actions were taken on the fakes (or whatever other conditions you deem necessary for success)
I would have expected to see something more like
// Arrange the fakes
var all = A.Fake<IScheduleHubClientContract>();
var clients = A.Fake<IHubCallerConnectionContext<dynamic>>();
A.CallTo(() => clients.All).Returns(all); // if All has a getter, this could be clients.All = all
// … and arrange the system under test
var hub = new ScheduleHub();
hub.Clients = clients;
// Act, by exercising the system under test
var id = "123456789";
hub.UpdateToCheckedIn(id);
// Assert - verify that the expected calls were made to the Fakes
A.CallTo(() => all.UpdateToCheckedIn(A<string>.Ignored)).MustHaveHappened();
To clarify what I'm trying to do, I have a method that creates/updates an item in my DB. When Testing as usual by asserting that the item is in the in-memory database always comes back as null. After reading the docs, it appears I need to be determining whether or not the correct job has been added to the Hangfire queue.
I've been going over the documentation for unit testing a Hangfire implementation but can't seem to find a way to test against in-memory storage.
In the example, a mock is created and then passed into the constructor of the controller. However, I've been modeling my solution after this example where jobs are queued in the Configure method of the Startup class. This means I have no controller and I'm not passing anything into my methods that are queuing up my jobs.
So how should I be going about testing this?
Here's one of the methods I'm trying to test:
public async Task SyncDeletedAccounts()
{
try
{
int pastMinutes = 90;
var startDate = DateTime.Now.AddMinutes(0 - pastMinutes);
var client = await this._forceDotComService.GetForceClient();
var deletedIds = await this._forceDotComService.GetDeletedIds<sf.Account>(client, startDate);
if (!deletedIds.Any())
{
this._logger.LogInformation("No deleted accounts");
return;
}
foreach (var deletedId in deletedIds)
{
BackgroundJob.Enqueue<InterconnectorServiceHelper<sf.Account, ic.Account>>(i =>
i.MarkDeleted(deletedId));
}
}
catch (Exception e)
{
this._logger.LogError(e, "AccountService.SyncDeletedAccounts");
throw;
}
}
There seems to be little information about how to write good unit tests for actual ASP.NET Core controller actions. Any guidance about how to make this work for real?
I've got a system that seems to be working pretty well right now, so I thought I'd share it and see if it doesn't help someone else out. There's a really useful article in the Entity Framework documentation that points the way. But here's how I incorporated it into an actual working application.
1. Create an ASP.NET Core Web App in your solution
There are tons of great articles out there to help you get started. The documentation for basic setup and scaffolding is very helpful. For this purpose, you'll want to create a web app with Individual User Accounts so that your ApplicationDbContext is setup to work with EntityFramework automatically.
1a. Scaffold a controller
Use the information included in the documentation to create a simple controller with basic CRUD actions.
2. Create a separate class library for your unit tests
In your solution, create a new .NET Core Library and reference your newly created web app. In my example, the model I'm using is called Company, and it uses the CompaniesController.
2a. Add the necessary packages to your test library
For this project, I use xUnit as my test runner, Moq for mocking objects, and FluentAssertions to make more meaningful assertions. Add those three libraries to your project using NuGet Package Manager and/or Console. You may need to search for them with the Show Prerelease checkbox selected.
You will also need a couple of packages to use EntityFramework's new Sqlite-InMemory database option. This is the secret sauce. Below are a list of the package names on NuGet:
Microsoft.Data.Sqlite
Microsoft.EntityFrameworkCore.InMemory [emphasis added]
Microsoft.EntityFrameworkCore.Sqlite [emphasis added]
3. Setup Your Test Fixture
Per the article I mentioned earlier, there is a simple, beautiful way to set up Sqlite to work as an in-memory, relational database which you can run your tests against.
You'll want to write your unit test methods so that each method has a new, clean copy of the database. The article above shows you how to do that on a one-off basis. Here's how I set up my fixture to be as DRY as possible.
3a. Synchronous Controller Actions
I've written the following method that allows me to write tests using the Arrange/Act/Assert model, with each stage acting as a parameter in my test. Below is the code for the method and the relevant class properties in the TestFixture that it references, and finally an example of what it looks like to call the code.
public class TestFixture {
public SqliteConnection ConnectionFactory() => new SqliteConnection("DataSource=:memory:");
public DbContextOptions<ApplicationDbContext> DbOptionsFactory(SqliteConnection connection) =>
new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(connection)
.Options;
public Company CompanyFactory() => new Company {Name = Guid.NewGuid().ToString()};
public void RunWithDatabase(
Action<ApplicationDbContext> arrange,
Func<ApplicationDbContext, IActionResult> act,
Action<IActionResult> assert)
{
var connection = ConnectionFactory();
connection.Open();
try
{
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
context.Database.EnsureCreated();
// Arrange
arrange?.Invoke(context);
}
using (var context = new ApplicationDbContext(options))
{
// Act (and pass result into assert)
var result = act.Invoke(context);
// Assert
assert.Invoke(result);
}
}
finally
{
connection.Close();
}
}
...
}
Here's what it looks like to call the code to test the Create method on the CompaniesController (I use parameter names to help me keep my expressions straight, but you don't strictly need them):
[Fact]
public void Get_ReturnsAViewResult()
{
_fixture.RunWithDatabase(
arrange: null,
act: context => new CompaniesController(context, _logger).Create(),
assert: result => result.Should().BeOfType<ViewResult>()
);
}
My CompaniesController class requires a logger, that I mock up with Moq and store as a variable in my TestFixture.
3b. Asynchronous Controller Actions
Of course, many of the built-in ASP.NET Core actions are asynchronous. To use this structure with those, I've written the method below:
public class TestFixture {
...
public async Task RunWithDatabaseAsync(
Func<ApplicationDbContext, Task> arrange,
Func<ApplicationDbContext, Task<IActionResult>> act,
Action<IActionResult> assert)
{
var connection = ConnectionFactory();
await connection.OpenAsync();
try
{
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
await context.Database.EnsureCreatedAsync();
if (arrange != null) await arrange.Invoke(context);
}
using (var context = new ApplicationDbContext(options))
{
var result = await act.Invoke(context);
assert.Invoke(result);
}
}
finally
{
connection.Close();
}
}
}
It's almost exactly the same, just setup with async methods and awaiters. Below, an example of calling these methods:
[Fact]
public async Task Post_WhenViewModelDoesNotMatchId_ReturnsNotFound()
{
await _fixture.RunWithDatabaseAsync(
arrange: async context =>
{
context.Company.Add(CompanyFactory());
await context.SaveChangesAsync();
},
act: async context => await new CompaniesController(context, _logger).Edit(1, CompanyFactory()),
assert: result => result.Should().BeOfType<NotFoundResult>()
);
}
3c. Async Actions with Data
Of course, sometimes you'll have to pass data back-and-forth between the stages of testing. Here's a method I wrote that allows you to do that:
public class TestFixture {
...
public async Task RunWithDatabaseAsync(
Func<ApplicationDbContext, Task<dynamic>> arrange,
Func<ApplicationDbContext, dynamic, Task<IActionResult>> act,
Action<IActionResult, dynamic> assert)
{
var connection = ConnectionFactory();
await connection.OpenAsync();
try
{
object data;
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
await context.Database.EnsureCreatedAsync();
data = arrange != null
? await arrange?.Invoke(context)
: null;
}
using (var context = new ApplicationDbContext(options))
{
var result = await act.Invoke(context, data);
assert.Invoke(result, data);
}
}
finally
{
connection.Close();
}
}
}
And, of course, an example of how I use this code:
[Fact]
public async Task Post_WithInvalidModel_ReturnsModelErrors()
{
await _fixture.RunWithDatabaseAsync(
arrange: async context =>
{
var data = new
{
Key = "Name",
Message = "Name cannot be null",
Company = CompanyFactory()
};
context.Company.Add(data.Company);
await context.SaveChangesAsync();
return data;
},
act: async (context, data) =>
{
var ctrl = new CompaniesController(context, _logger);
ctrl.ModelState.AddModelError(data.Key, data.Message);
return await ctrl.Edit(1, data.Company);
},
assert: (result, data) => result.As<ViewResult>()
.ViewData.ModelState.Keys.Should().Contain((string) data.Key)
);
}
Conclusion
I really hope this helps somebody getting on their feet with C# and the awesome new stuff in ASP.NET Core. If you have any questions, criticisms, or suggestions, please let me know! I'm still new at this, too, so any constructive feedback is invaluable to me!