I am using Mass Transit, Entity Framework, C# in my project.
I have my consumer consuming an event and which insert data in to the table. I would like to know how to mock the consumer and unit test case for this method.
public async Task Consume(ConsumeContext<MyEvent> context)
{
private readonly MyDbContext _dbContext;
try
{
// Here logic to insert record in to new database
var data = new MyService.TableNmae()
{
Id = context.Message.MyId,
Description = "test data"
};
_ = _dbContext.TableName.AddAsync(data);
_ = _dbContext.SaveChangesAsync(context.CancellationToken);
}
catch (Exception ex)
{
_logger.LogCritical($"{GetType().Name}:{nameof(Consume)} {ex}");
}
await Task.CompletedTask;
}
Here is my unit test case code i have added
{
private ITestHarness _testHarness;
[SetUp]
public void Initialize()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransitTestHarness(busRegistrationConfigurator =>
{
busRegistrationConfigurator.AddConsumer<MyConsumer>();
});
var serviceProvider = serviceCollection.BuildServiceProvider();
_testHarness = serviceProvider.GetRequiredService<ITestHarness>();
}
[Test]
public async Task TestMethod1()
{
await _testHarness.Start();
await _testHarness.Bus.Publish(new MyEvent { Code = "H"});
Assert.That(await _testHarness.Published.Any<MyEvent>(), Is.True);
Assert.That(await _testHarness.Consumed.Any<MyEvent>(), Is.True);
}
}
Expected: True
But was: False.
Here the first assert is true but second assert always false,
Instead of trying to mock ConsumeContext<T>, I recommend using the MassTransit test harness, which is documented on the web site.
As others have pointed out, testing simply that your entity was saved is a pretty trivial test, and more of an integration test assuming you are verifying that the entity was saving all required properties, etc. Not something I would test in isolation, vs. ensuring the data is available for higher level operations being tested.
Related
I'm trying to unit test my state machine and that it transitions to the states I expect when certain events are received. To do this I'm using the InMemoryTestHarness which enables me to create my state machine and then using the bus I can publish messages to it.
For the most part this is fine. However, there are a couple of examples where it then goes off and does an activity before it transitions state like this:
During(Submitted,
When(OnFlyCoolThingRequest)
.Activity(activitySelector => activitySelector.OfType<MakeItFlyActivity>()).TransitionTo(Flying));
At this point I don't want to test the activity and as it happens it appears that it seems to actually break the test (just hangs). My test is currently setup like this:
[Test]
public async Task CoolThingSagaShouldTransitionToFlyingStateOnFlyCoolThingRequestEvent()
{
var stateMachine = new CoolStateMachine();
var harness = new InMemoryTestHarness();
var saga = harness.StateMachineSaga<CoolThingSaga, CoolStateMachine>(new CoolStateMachine());
await harness.Start();
try
{
var coolThing = GetCoolThing();
// Create a CoolThing instance.
await harness.Bus.Publish(new CoolThingCreated(coolThing.Id));
var instanceIdSubmitted = await saga.Exists(coolThing.Id, state => state.Submitted);
// Publish a FlyCoolThingRequest event for the above instance.
await harness.Bus.Publish(new FlyCoolThingRequest(coolThing.Id, DateTime.UtcNow));
var instanceIdFlying = await saga.Exists(coolThing.Id, state => state.Flying);
Assert.That(instanceIdSubmitted.Value, Is.EqualTo(instanceIdFlying.Value), $"Instance id returned between the state of '{stateMachine.Submitted.Name}' and '{stateMachine.Flying.Name}' should be the same.");
Assert.That(instanceIdFlying, Is.Not.Null, $"A CoolThingSaga instance should have been created with Id {instanceIdFlying} and a be in the state of '{stateMachine.Flying}'.");
}
finally
{
await harness.Stop();
}
}
At the moment this type of test works for testing states that don't have activities associated with them. However, the tests seem to hang when an activity is involved. So how can I mock the activity so it doesn't actually try and perform that logic?
UPDATE
Looks like it fails to create the activity due to not being able to find a parameterless constructor. My activity is akin to this:
public sealed class MakeItFlyActivity : Activity<CoolThingSaga, FlyCoolThingRequest>
{
private readonly FlyingService _flyingService;
public MakeItFlyActivity(FlyingService flyingService)
{
_flyingService = flyingService;
}
public async Task Execute(BehaviorContext<CoolThingSaga, FlyCoolThingRequest> context, Behavior<CoolThingSaga, FlyCoolThingRequest> next)
{
await flyingService.MakeItFly(context.Instance.Details.Id);
await next.Execute(context);
}
public void Accept(StateMachineVisitor visitor) => visitor.Visit(this);
public async Task Faulted<TException>(BehaviorExceptionContext<CoolThingSaga, FlyCoolThingRequest, TException> context, Behavior<CoolThingSaga, FlyCoolThingRequest> next)
where TException : Exception => await next.Faulted(context);
public void Probe(ProbeContext context) => context.CreateScope(nameof(MakeItFlyActivity));
}
My setup is as follows:
private InMemoryTestHarness fHarness;
private StateMachineSagaTestHarness<CoolThingSaga, CoolStateMachine> fSaga;
private CoolStateMachine fStateMachine;
[OneTimeSetUp]
public async Task Setup()
{
var provider = new ServiceCollection()
.AddSingleton<ILoggerFactory>(p => new TestOutputLoggerFactory(true))
.AddMassTransitInMemoryTestHarness(cfg =>
{
cfg.AddSagaStateMachine<CoolStateMachine, CoolThingSaga>().InMemoryRepository();
cfg.AddSagaStateMachineTestHarness<CoolStateMachine, CoolThingSaga>();
})
.AddSingleton(p => new FlyingService(TimeSpan.FromMinutes(15), NullLogger<FlyingService>.Instance))
.AddScoped<MakeItFlyActivity>()
.BuildServiceProvider(true);
fHarness = provider.GetRequiredService<InMemoryTestHarness>();
fSaga = fHarness.StateMachineSaga<CoolThingSaga, CoolStateMachine>(new CoolStateMachine());
fStateMachine = provider.GetRequiredService<CoolStateMachine>();
await fHarness.Start();
}
To test state machines, you should be using the container based test harness as outlined in the documentation. This will ensure that activities and their dependencies can be resolved at runtime. Your test likely hangs because the activity could not be created.
The documentation for v8 is linked, you can also refer to the v7 documentation if you're using an earlier version.
Problem:
I am new to writing unit testing in c# using xunit. So I am trying to mock the MongoDB connection. In my project I have use repository pattern there I have used unit of work class like this. so I am accessing every repository via it. so the unit of work class code is here.
namespace QuestionBank.API.Repositories
{
public class UnitOfWork : IUnitOfWork
{
public readonly IQuestionsBankDBContext _context;
private readonly ILogger<UnitOfWork> _logger;
private Dictionary<Type, object> repositories;
private IQuestionsRepository _questionsRepository;
private ICampaignQuestionsRepository _campaignQuestionsRepository;
private ICandidateAnswerRepository _candidateAnswerRepository;
private IIntergrationEventLogRepository _integrationEventLogRepository;
private IControlRepository _controlRepository;
public UnitOfWork(IQuestionsBankDBContext context, ILogger<UnitOfWork> logger)
{
_context = context;
_logger = logger;
}
public IQuestionsRepository QuestionsRepository
{
get
{
this._questionsRepository = new QuestionsRepository(_context as IQuestionsBankDBContext, this, _logger);
return this._questionsRepository;
}
}
public ICandidateAnswerRepository CandidateAnswerRepository
{
get
{
this._candidateAnswerRepository = new CandidateAnswerRepository(_context as IQuestionsBankDBContext, this, _logger);
return this._candidateAnswerRepository;
}
}
public ICampaignQuestionsRepository CampaignQuestionsRepository
{
get
{
this._campaignQuestionsRepository = new CampaignQuestionsRepository(_context as IQuestionsBankDBContext, this, _logger);
return this._campaignQuestionsRepository;
}
}
public IIntergrationEventLogRepository IntegrationEventLogRepository
{
get
{
this._integrationEventLogRepository = new IntergrationEventLogRepository(_context as IQuestionsBankDBContext, this, _logger);
return this._integrationEventLogRepository;
}
}
public IControlRepository ControlRepository
{
get
{
this._controlRepository = new ControlRepository(_context as IQuestionsBankDBContext, this, _logger);
return this._controlRepository;
}
}
public IGenericRepository<TDocument> GetRepository<TDocument>() where TDocument : IDocument
{
if (this.repositories == null)
{
this.repositories = new Dictionary<Type, object>();
}
var type = typeof(TDocument);
if (!this.repositories.ContainsKey(type))
{
this.repositories[type] = new GenericRepository<TDocument>(_context);
}
return (IGenericRepository<TDocument>)this.repositories[type];
}
}
}
So in the unit test to mock services and repositories, I need to pass database context to unitofwork. I tried it this way.
var mockDbContext = new Mock<QuestionsBankDBContext>();
var dbContext = mockDbContext.Object;
var mock = new Mock<ILogger<UnitOfWork>>();
_logger = mock.Object;
unitOfWork = new UnitOfWork(dbContext, _logger);
questionsService = new QuestionsService(unitOfWork);
campaignQuestionsService = new CampaignQuestionsService(unitOfWork);
tokenService = new TokenService();
stringLocalizer = new Mock<IStringLocalizer<SharedResource>>();
questionBankIntergrationEventService = new Mock<IQuestionBankIntergrationEventService>();
questionsController = new QuestionsController(questionsService, campaignQuestionsService, stringLocalizer.Object, tokenService, questionBankIntergrationEventService.Object);
contextMock = new Mock<HttpContext>();
And this is my DB context class.
using MongoDB.Driver;
using QuestionBank.API.Models;
namespace QuestionBank.API.Data
{
public class QuestionsBankDBContext : IQuestionsBankDBContext
{
public IMongoClient Client { get; set; }
public IMongoDatabase Database { get; set; }
public QuestionsBankDBContext(IQuestionBankDatabaseSettings settings)
{
Client = new MongoClient(settings.ConnectionString);
Database = Client.GetDatabase(settings.DatabaseName);
}
}
}
Then I wrote a unit test like this.
[Theory]
[InlineData("61879e54e86be1fa5e41831f")]
[InlineData("61879e54e86be1fa5e41831e")]
public async Task GetQuestionById(string questionId)
{
var actionResult = await questionsController.GetQuestionById(questionId);
var result = actionResult as ObjectResult;
Assert.NotNull(result.Value);
if (result.StatusCode == (int)System.Net.HttpStatusCode.OK)
{
Assert.IsType<Questions>(result.Value);
}
else if (result.StatusCode == (int)System.Net.HttpStatusCode.NotFound)
{
Assert.Contains("ErrorCode", result.Value.ToString());
}
else if (result.StatusCode == (int)System.Net.HttpStatusCode.InternalServerError)
{
var code = (int)ErroCodes.InternalServerError;
Assert.Contains(code.ToString(), result.Value.ToString());
}
}
Then when running this it gives
And my question controller GetQuestionById is like this.
[HttpGet]
//[Authorize(Roles = "SuperAdmin,Admin")]
[Route("getquestionbyidfrombank")]
[ProducesResponseType(typeof(Questions), 200)]
[ProducesResponseType(typeof(string), 404)]
[ProducesResponseType(typeof(string), 500)]
public async Task<IActionResult> GetQuestionById([FromQuery] string questionId)
{
try
{
string errorText;
if (!string.IsNullOrEmpty(questionId))
{
var question = await
questionsService.GetQuestionById(questionId);
return Ok(question);
}
else
{
errorText = string.Format(stringLocalizer[Constants.ErrorCodeString],
(int)ErroCodes.SpecifiedItemNotFound,
stringLocalizer[Helper.ToEnumString(ErroCodes.SpecifiedItemNotFound)]);
return StatusCode(404, errorText);
}
}
catch (Exception ex)
{
string exceptionData =
$"Exception occured while getiing question by id. " +
$"\nException Data: Message- {ex.Message}; " +
$"InnerException- {ex.InnerException}; StackTrace- {ex.StackTrace}";
string errorText = string.Format(stringLocalizer[Constants.ErrorCodeString],
(int)ErroCodes.InternalServerError,
stringLocalizer[Helper.ToEnumString(ErroCodes.InternalServerError)]);
return StatusCode(500, errorText);
}
}
this is how I do the service instantiation.
public QuestionsService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
Get question by id function
public async Task<Questions> GetQuestionById(string id)
{
var question = await _unitOfWork.QuestionsRepository.FindByIdAsync(id);
return question;
}
Can someone help me to write this unit test correctly and solve this issue. I tried a lot to find out a way to do this but I could not able to do it. Thank you
Don't - just because something can be mocked, doesn't mean it should.
Instead, you can a docker image to run Mongo, drop and create a DB per test class init, drop collections per test init.
Testing a DAL (data-access-layer) without the DB is a waste of time and won't help you actually find bugs.
When you unit test your other components, mock the entire DAL to return the objects you expect.
And do not skip writing a test for your entire service that includes an empty/pre-populated-via-test-config-data DB.
Also, the fields in your DbContext should be private, not public and while passing a constant instance of a DbContext to MongoDB is OK, because MongoDB context is stateless (no transactions and connections are pooled), in general passing a DbContext via constructor is wrong (because relational DBs have transactions and the connections should not be kept open), instead pass a Func<DbContext> (e.g. public MyClass(Func<DbContext> contextConstructor)) which returns the constructor and have DbContext implement IDisposable. This way the client class can do using (context = contextCreator()) { ... }.
You should create a mock of the interface IQuestionsBankDBContext not the class QuestionsBankDBContext. Apart from that your test is not a unit test, it's more of an integration test because you are creating an instance of a controller and some services, and you mock only the database layer of your application. In unit tests you should test only a single layer. If you write unit tests for a controller then you should mock the services and their behavior (the direct dependencies of the controller).
Looking at your controller method, first unit test should check that when you pass not null and not empty question id then your method returns OkResult. For this test the mock of questionsService could return a question object.
Next test should check the opposite case, when it gets empty string it should return a response with code 404. (In my opinion it should be 400 but we are talking about unit tests)
Last test should check the exception handling. For example you pass a valid questionId in the test, but the mock of questionService throws an exception. In this test you can assert that the response has status code 500.
If you want to test many cases with one test method (a Theory in XUnit) then all these cases should have the same result, or you should provide an expected result with each case.
Does any one know how to write a unit test (using xUnit) for the following Get() Method?
Get() method is in the controller and returns list of all Categories:
public class CategoryController : Controller
{
private MyContext x;
public CategoryController(MyContext y)
{
x = y;
}
[HttpGet]
public ActionResult<IEnumerable<Category>> Get()
{
return x.Categories.ToList();
}
}
If you are using EFCore as ORM, you can use InMemory database for unit testing.
There simple example:
[Fact]
public void TestGet()
{
_options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "default")
.Options;
var context = new MyContext(_options);
context.EnsureSeed();
var controller = new CategoryController(context);
//Act
var results = controller.Get();
//Assert
Assert.NotNull(results);
Assert.True(results.Count > 0, "Expected to be greater than 0.");
}
Also you need implement EnsureSeed method. Example:
public static void EnsureSeed(this MyContext dataContext)
{
//Check if database is created, if not - create
dataContext.Database.EnsureCreated();
var category = new Category()
{
Id = 1
};
dataContext.Categories.Add(category);
dataContext.SaveChanges();
}
From what I've seen and read the best way to unit test a controller function is to create an instance of the server host from your test setup and make requests directly to your endpoint - this will allow you test the transport layer of your application like the API contract and Http protocols.
The following is an example implemented in .Net Core:
[Trait]
public class CategoryControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
// Startup - the entry point of most .net core project
private readonly WebApplicationFactory<Startup> _factory;
public CategoryControllerTests(WebApplicationFactory<Startup> factory)
{
// Any webhost config needed to run tests against the test
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
// register any mock dependancies there - any dependencies in Startup.cs will hold unless overridden by a mock
services.AddScoped(x => new Mock() );
});
});
}
[Fact]
public async Task Get_ValidRequest_ReturnsData()
{
var client = _factory.CreateClient();
// Whatever routing protocol you use to define your endpoints
var url = "/category";
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsAsync<List<Category>>();
// Any asserts on your response
Assert.NotNull(content);
}
}
This is dependant on how you have setup the startup/initialisation of your project, it will allow you to test the project as though it were running in a production environment while letting you mock out any dependancies below the transport layer for a true unit test.
Note: the use of IClassFixture<WebApplicationFactory> - this will let you reuse an instance of WebApplicationFactory<Startup> for faster test execution; XUnit will inject this for you as part of the framework.
Summary:
I'm trying to test a controller with an ActionFilter implementation
Unit test fails, because ActionFilter does not get invoked in unit test.
Testing via Postman works as expected and the correct result is achieved.
Can the controller be tested like this or should it move to integration test?
Breakdown:
I'm able to test the ActionFilter on its own in a unit test, what I would like to do is test the controller in a unit test.
The action filter looks like this:
public class ValidateEntityExistAttribute<T> : IActionFilter
where T : class, IEntityBase
{
readonly AppDbContext _appDbContext;
public ValidateEntityExistAttribute(AppDbContext appDbContext)
{
this._appDbContext = appDbContext;
}
public void OnActionExecuted(ActionExecutedContext context)
{}
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ActionArguments.ContainsKey("id"))
{
context.Result = new BadRequestObjectResult("The id must be passed as parameter");
return;
}
int id = (int)context.ActionArguments["id"];
var foundEntity = _appDbContext.Set<T>().Find(id);
if (foundEntity == null)
context.Result = new NotFoundResult();
else
context.HttpContext.Items.Add("entity_found", foundEntity);
}
}
The ActionFilter implementation is added to the services in the startup file
ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<ValidateEntityExistAttribute<Meeting>>();
...
}
The filter can be applied to any controller method that needs to check if an entity exists. ie the GetById method.
[HttpGet("{id}")]
[ServiceFilter(typeof(ValidateEntityExistAttribute<Meeting>))]
public async Task<ActionResult<MeetingDto>> GetById(int id)
{
var entity = HttpContext.Items["entity_found"] as Meeting;
await Task.CompletedTask;
return Ok(entity.ConvertTo<MeetingDto>());
}
In the xUnit test I have set up the test to test the controller like this:
[Fact]
public async Task Get_Meeting_Record_By_Id()
{
// Arrange
var _AppDbContext = AppDbContextMocker.GetAppDbContext(nameof(Get_All_Meeting_Records));
var _controller = InitializeController(_AppDbContext);
//Act
var all = await _controller.GetById(1);
//Assert
Assert.Equal(1, all.Value.Id);
//clean up otherwise the other test will complain about key tracking.
await _AppDbContext.DisposeAsync();
}
and this is what the InitializeController method look like, I left the commented lines so that it is visible to what I have tried, none of the commented code worked.
I mocked and used the default classes.
private MeetingController InitializeController(AppDbContext appDbContext)
{
var _controller = new MeetingController(appDbContext);
var spf = new DefaultServiceProviderFactory(new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true });
var sc = spf.CreateBuilder(new ServiceCollection());
sc.AddMvc();
sc.AddControllers();
//(config =>
//{
// config.Filters.Add(new ValidateModelStateAttribute());
// config.Filters.Add(new ValidateEntityExistAttribute<Meeting>(appDbContext));
//});
sc.AddTransient<ValidateModelStateAttribute>();
sc.AddTransient<ValidateEntityExistAttribute<Meeting>>();
var sp = sc.BuildServiceProvider();
//var mockHttpContext = new Mock<HttpContext>();
var httpContext = new DefaultHttpContext
{
RequestServices = sp
};
//mockHttpContext.Setup(cx => cx.RequestServices).Returns(sp);
//var contDesc = new ControllerActionDescriptor();
//var context = new ControllerContext();
//var context = new ControllerContext(new ActionContext(mockHttpContext.Object, new RouteData(), contDesc));
//context.HttpContext = mockHttpContext.Object;
//context.HttpContext = httpContext;
//_controller.ControllerContext = context;
_controller.ControllerContext.HttpContext = httpContext;
return _controller;
}
The issues I have is that when running the unit test the ActionFilter implementation is never invoked, thus breaking the test because var entity = HttpContext.Items["entity_found"] as Meeting; in the controller is always null! more accurately HttpContext.Items is always null.
The break point in the ActionFilter never gets hit.
When this is tested via postman it all works as expected, and the break point gets hit
Is there a way to test the controller as a unit test this way, or should this test now just move to integration?
Thank you #Fei Han for the link about unit testing controllers.
As it turns out this is by design, as stated in the microsoft documentation
Unit testing controllers
Set up unit tests of controller actions to
focus on the controller's behavior. A controller unit test avoids
scenarios such as filters, routing, and model binding. Tests that
cover the interactions among components that collectively respond to a
request are handled by integration tests.
So the test should move to integration testing.
In this particular scenario, where there is no detailed implementation for the GetById(int id) method, there is almost no value in doing a unit test for it.
If the GetById(int id) method had a more complex implementation, or if the ActionFilter did not prevent further processing, the HttpContext.Items["entity_found"] should be mocked.
The exact situation is I'm doing E2E tests with Protractor.NET (.NET port of AngularJS's Protractor E2E framework) and I would like to make some web requests (and the API -- System.Net.Http.HttpClient -- has all Async/Task methods) to Arrange my test before I Act/Assert, only I need to do this same Arrange-ing for several tests.
I'm using xUnit.net as my test runner they use an interface (IUseFixture<T>) for per-fixture setup code. It would be nice if there was a IAsyncUseFixture<T> that had a Task SetFixtureAsync(T t); or something. I don't think such a thing exists. Additionally I don't think constructors can use await either, and constructors are the only other way to execute the same block of code per-test in xUnit.net.
What are my options? .Result? Isn't that bad practice (deadlock)?
xUnit has an IAsyncLifetime interface for async setup/teardown. The methods you need to implement are Task InitializeAsync() and Task DisposeAsync().
InitializeAsync is called immediately after the class has been created, before it is used.
DisposeAsync is called just before IDisposable.Dispose if the class also implements IDisposable.
e.g.
public class MyTestFixture : IAsyncLifetime
{
private string someState;
public async Task InitializeAsync()
{
await Task.Run(() => someState = "Hello");
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
[Fact]
public void TestFoo()
{
Assert.Equal("Hello", someState);
}
}
I would use AsyncLazy
http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html
In my case I want to run some integration tests against a self hosted web api.
public class BaseTest()
{
private const string baseUrl = "http://mywebsite.web:9999";
private static readonly AsyncLazy<HttpSelfHostServer> server = new AsyncLazy<HttpSelfHostServer>(async () =>
{
try
{
Log.Information("Starting web server");
var config = new HttpSelfHostConfiguration(baseUrl);
new Startup()
.Using(config)
.Add.AttributeRoutes()
.Add.DefaultRoutes()
.Remove.XmlFormatter()
.Serilog()
.Autofac()
.EnsureInitialized();
var server = new HttpSelfHostServer(config);
await server.OpenAsync();
Log.Information("Web server started: {0}", baseUrl);
return server;
}
catch (Exception e)
{
Log.Error(e, "Unable to start web server");
throw;
}
});
public BaseTest()
{
server.Start()
}
}