Cannot setup a Moq callback for MediatR - c#

I'm writing a test that verifies if my controller calls my query with expected query arguments. This is my query class:
public class GetProducts : IRequest<IEnumerable<Product>>
{
public int CategoryId { get; set; }
}
This implements IRequest<T> MediatR interface.
This is the test case that does not work:
[Theory]
[InlineData(1)]
public async Task GetProductsAsync_GivenValidRequestParameters_ReturnsListGetProductsResponseAsync(int categoryId)
{
var expectedQuery = new GetProducts
{
CategoryId = categoryId
};
_mediatorMock
.Setup(s => s.Send(It.IsAny<GetProducts>(), It.IsAny<CancellationToken>()))
.Callback<GetProducts, CancellationToken>((query, ct) => query.Should().BeEquivalentTo(expectedQuery))
.ReturnsAsync(Enumerable.Empty<Product>());
var response = await _productsController.GetProductsAsync(categoryId);
response.Result.Should().BeOfType<OkObjectResult>();
_mediatorMock.Verify(s => s.Send(It.IsAny<GetProducts>(), It.IsAny<CancellationToken>()), Times.Once);
}
This is the controller I'm testing:
[ApiController]
[Route("categories/{categoryId:int}/products")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[TrackUsage]
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
[HttpGet]
public async Task<ActionResult<IEnumerable<GetProductsResponse>>> GetProductsAsync([FromRoute]int categoryId)
{
var query = new GetProducts
{
CategoryId = categoryId
};
var products = await _mediator.Send(query);
return Ok(products.ToResponse());
}
}
It complains because it cannot find an callback with <GetProducts, CancellationToken> as parameters even though it seems right.
I know I could use It.Is<...>(callback => true) to check each and every property, but there could be queries with multiple properties and I'd prefer to test that using FluentAssertion.

Generic Send definition
public interface IMediator
{
/// <summary>
/// Asynchronously send a request to a single handler
/// </summary>
/// <typeparam name="TResponse">Response type</typeparam>
/// <param name="request">Request object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the send operation. The task result contains the handler response</returns>
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
//...
Source
The Callback needs to match the provided definition
//...
_mediatorMock
.Setup(s => s.Send<IEnumerable<Product>>(It.IsAny<IRequest<IEnumerable<Product>>>(), It.IsAny<CancellationToken>()))
.Callback<IRequest<IEnumerable<Product>>, CancellationToken>((query, ct) =>
((GetProducts)query).Should().BeEquivalentTo(expectedQuery)
)
.ReturnsAsync(Enumerable.Empty<Product>());
//...

Related

Moq Service Bus using xUnit in Azure Function

I have Azure Function which implements the HTTP Triggers and Service Bus. I have managed to complete xUnits implementation for Http Response but not sure how I mock Azure Service Bus. I don't want the code actual create Service Bus Message in the Queue.
[FunctionName("MyFunction1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "POST")] HttpRequest req
,[ServiceBus("providerexemptionreceivednotification", Connection = "ProviderExemptionReceivedNotification")] IAsyncCollector<Message> servicebusMessage
)
{
//code
await servicebusMessage.AddAsync(ringGoExemptionMessage); //throw Null exception:
}
Error
xUnit Test
private readonly Mock<IAsyncCollector<Message>> servicebusMessage;
[Fact]
public void
Function_ShouldReturn_SuccessResponseResultObject_WhenSuccess()
{
//Arrange
var fixture = new Fixture();
var ringGoTransaction = GetRingGoTestData();
Mock<HttpRequest> mockHttpRequest = CreateMockRequest(ringGoTransaction);
var providerLocationDataMoq = (1, fixture.Create<ProviderLocation>());
providerExemptionServiceMoq.Setup(x => x.GetProviderLocation(13, "222")).ReturnsAsync(providerLocationDataMoq);
//Assert
var actualResult = sut.Run(mockHttpRequest.Object, (IAsyncCollector<Microsoft.Azure.ServiceBus.Message>)servicebusMessage.Object); //???????????
//Act
}
Test Helper Class
private static Mock<HttpRequest> CreateMockRequest(object body)
{
var memoryStream = new MemoryStream();
var writer = new StreamWriter(memoryStream);
var json = JsonConvert.SerializeObject(body);
writer.Write(json);
writer.Flush();
memoryStream.Position = 0;
var mockRequest = new Mock<HttpRequest>();
mockRequest.Setup(x => x.Body).Returns(memoryStream);
mockRequest.Setup(x => x.ContentType).Returns("application/json");
return mockRequest;
}
mock service bus error
I was not seeing where you mocked IAsyncCollector<Message>.
It looks like an interface
public interface IAsyncCollector<in T>
{
/// <summary>
/// Adds an item to the <see cref="IAsyncCollector{T}"/>.
/// </summary>
/// <param name="item">The item to be added.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that will add the item to the collector.</returns>
Task AddAsync(T item, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Flush all the events accumulated so far.
/// This can be an empty operation if the messages are not batched.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns></returns>
Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
}
Source
so should be simple enough with new Mock<IAsyncCollector<Message>>() and setting up the members used to exercise the test
For example
[Fact]
public async Task Function_ShouldReturn_SuccessResponseResultObject_WhenSuccess() {
//Arrange
//... removed for brevity
Mock<IAsyncCollector<Message>> servicebusMessage = new Mock<IAsyncCollector<Message>>();
servicebusMessage
.Setup(_ => _.AddAsync(It.IsAny<Message>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
//Act
await sut.Run(mockHttpRequest.Object, servicebusMessage.Object);
//Assert
//...
}
Given the asynchronous nature of the subject under test, note that the test case has also been made asynchronous.

.Net Core not routing to POST Method but GET Method works

I'm training with the sample web application.
For unknown reason .Net Core does not route POST requests to corresponding method.
Get methods are working but POST not even being fired.
POST requests return 404.
I already tried to add annotation like that but it didn't help.
Could someone pleas
[HttpPost("envelope")]
Here's the code of my controller.
namespace Receiver.Controllers
{
[Route("[controller]")]
[ApiController]
public class EnvelopesController : ControllerBase
{
private readonly ReceiverDbContext _dbContext;
private readonly ILogger _logger;
public EnvelopesController(ReceiverDbContext dbContext, ILogger logger)
{
_dbContext = dbContext;
_logger = logger;
}
/// <summary>
/// GET (Read all) /envelopes
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerator<EnvelopeDTO>>> GetAll()
{
_logger.LogInformation("Hello from the Get() method!");
var envelopes = await _dbContext.Envelope.Include(e => e.Type).ToArrayAsync();
return Ok(envelopes.Select(e => e.ToDTO()));
}
/// <summary>
/// GET (Read) /envelope/{id}
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<EnvelopeDTO>> Get(int id)
{
var envelope = await _dbContext.Envelope.Include(e => e.Type).FirstOrDefaultAsync(s => s.Id == id);
if (envelope == null)
return NotFound();
return Ok(envelope.ToDTO());
}
/// <summary>
/// POST (Create) /envelope
/// </summary>
/// <param name="envelopeDto"></param>
/// <returns></returns>
[HttpPost("envelope")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<EnvelopeDTO>> Create([FromBody] EnvelopeDTO envelopeDto)
{
_logger.LogInformation("Hello from the Post() method!");
if (string.IsNullOrEmpty(envelopeDto.СontentX))
return BadRequest();
var #type = await _dbContext.EnvelopeType.FirstOrDefaultAsync(t => t.Id == envelopeDto.TypeId);
if (#type == null)
return NotFound();
var #user = await _dbContext.User.FirstOrDefaultAsync(u => u.UserName == envelopeDto.User);
if (#user == null)
return NotFound();
var existingEnvelope = await _dbContext.Envelope.FindAsync(envelopeDto.СontentX);
if (existingEnvelope != null)
return Conflict();
var envelopeToAdd = envelopeDto.ToModel(#type, #user);
_dbContext.Envelope.Add(envelopeToAdd);
await _dbContext.SaveChangesAsync();
var updatedEnvelopeDto = envelopeToAdd.ToDTO();
return CreatedAtAction(nameof(Get), new { Content = envelopeDto.СontentX }, updatedEnvelopeDto);
}
/// <summary>
/// DELETE /envelope/{id}
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<EnvelopeDTO>> Delete(int id)
{
var envelope = await _dbContext.Envelope.FindAsync(id);
if (envelope == null)
return NotFound();
_dbContext.Envelope.Remove(envelope);
await _dbContext.SaveChangesAsync();
return Ok(envelope.ToDTO());
}
}
public static class EnvelopeExtensions
{
public static Envelope ToModel(this EnvelopeDTO envelopeDto, EnvelopeType #type, User #user)
{
//if (#class.Id != studentDto.ClassId) throw new NotSupportedException();
return new Envelope
{
//DateTimeReceivied = DateTime.Now,
Type = #type,
СontentX = envelopeDto.СontentX,
User = #user
};
}
public static EnvelopeDTO ToDTO(this Envelope envelope)
{
return new EnvelopeDTO
{
//DateTimeReceivied = DateTime.Now,
TypeId = envelope.Type.Id,
СontentX = envelope.СontentX,
User = envelope.User.UserName
};
}
}
}
Here are the samples of POST and GET requests.
Change
[HttpPost("envelope")]
to
[HttpPost]
The way you have it coded the POST body would be posted to the POST /envelopes/envelope endpoint, which appears to be what you don't want.
Also your comments in the other methods are misleading. For example
// DELETE /envelope/{id}
should really be
// DELETE /envelopes/{id}.
The [controller] portion of [Route("[controller]")] will be substituted with the lowercase portion of the controller name without Controller (i.e. /envelopes (plural) forms the basis for your endpoints from EnvelopesController.
In your Postman screenshots you can see that you were trying to GET from plural /envelopes (good) but post to singular /envelope, which is not defined.

How to successfully drive a MassTransitStateMachine via the InMemoryTestHarness?

Following up to: How to write MassTransitStateMachine unit tests?
Here's a simple test class (using MS Test) for a simple state machine called ProcedureStateMachine (note: this is not a real production state machine for us... just an experiment I'd used to play around with MassTransitStateMachine a while back.. it seemed a handy self-contained place to experiment with getting unit testing going too):
[TestClass]
public class ProcedureStateMachineTests
{
private ProcedureStateMachine _machine;
private InMemoryTestHarness _harness;
private StateMachineSagaTestHarness<ProcedureContext, ProcedureStateMachine> _saga;
[TestInitialize]
public void SetUp()
{
_machine = new ProcedureStateMachine();
_harness = new InMemoryTestHarness();
_saga = _harness.StateMachineSaga<ProcedureContext, ProcedureStateMachine>(_machine);
_harness.Start().Wait();
}
[TestCleanup]
public void TearDown()
{
_harness.Stop().Wait();
}
[TestMethod]
public async Task That_Can_Start()
{
// Arrange
// Act
await _harness.InputQueueSendEndpoint.Send(new BeginProcessing
{
ProcedureId = Guid.NewGuid(),
Steps = new List<string> {"A", "B", "C" }
});
// Assert
var sagaContext = _saga.Created.First();
sagaContext.Saga.RemainingSteps.ShouldHaveCountOf(2);
}
}
And here's the state machine class itself:
public class ProcedureStateMachine : MassTransitStateMachine<ProcedureContext>
{
public State Processing { get; private set; }
public State Cancelling { get; private set; }
public State CompleteOk { get; private set; }
public State CompleteError { get; private set; }
public State CompleteCancelled { get; private set; }
public Event<BeginProcessing> Begin { get; private set; }
public Event<StepCompleted> StepDone { get; private set; }
public Event<CancelProcessing> Cancel { get; private set; }
public Event<FinalizeProcessing> Finalize { get; private set; }
public ProcedureStateMachine()
{
InstanceState(x => x.CurrentState);
Event(() => Begin);
Event(() => StepDone);
Event(() => Cancel);
Event(() => Finalize);
BeforeEnterAny(binder => binder
.ThenAsync(context => Console.Out.WriteLineAsync(
$"ENTERING STATE [{context.Data.Name}]")));
Initially(
When(Begin)
.Then(context =>
{
context.Instance.RemainingSteps = new Queue<string>(context.Data.Steps);
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(Begin)}]: Procedure [{context.Data.ProcedureId}] Steps [{string.Join(",", context.Data.Steps)}]"))
.Publish(context => new ExecuteStep
{
ProcedureId = context.Instance.CorrelationId,
StepId = context.Instance.RemainingSteps.Dequeue()
})
.Publish(context => new SomeFunMessage
{
CorrelationId = context.Data.CorrelationId,
TheMessage = $"Procedure [{context.Data.CorrelationId} has begun..."
})
.TransitionTo(Processing)
);
During(Processing,
When(StepDone)
.Then(context =>
{
if (null == context.Instance.AccumulatedResults)
{
context.Instance.AccumulatedResults = new List<StepResult>();
}
context.Instance.AccumulatedResults.Add(
new StepResult
{
CorrelationId = context.Instance.CorrelationId,
StepId = context.Data.StepId,
WhatHappened = context.Data.WhatHappened
});
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(StepDone)}]: Procedure [{context.Data.ProcedureId}] Step [{context.Data.StepId}] Result [{context.Data.WhatHappened}] RemainingSteps [{string.Join(",", context.Instance.RemainingSteps)}]"))
.If(context => !context.Instance.RemainingSteps.Any(),
binder => binder.TransitionTo(CompleteOk))
.If(context => context.Instance.RemainingSteps.Any(),
binder => binder.Publish(context => new ExecuteStep
{
ProcedureId = context.Instance.CorrelationId,
StepId = context.Instance.RemainingSteps.Dequeue()
})),
When(Cancel)
.Then(context =>
{
context.Instance.RemainingSteps.Clear();
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(Cancel)}]: Procedure [{context.Data.ProcedureId}] will be cancelled with following steps remaining [{string.Join(",", context.Instance.RemainingSteps)}]"))
.TransitionTo(Cancelling)
);
During(Cancelling,
When(StepDone)
.Then(context =>
{
context.Instance.SomeStringValue = "Booo... we cancelled...";
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(StepDone)}]: Procedure [{context.Data.ProcedureId}] Step [{context.Data.StepId}] completed while cancelling."))
.TransitionTo(CompleteCancelled));
During(CompleteOk, When(Finalize).Finalize());
During(CompleteCancelled, When(Finalize).Finalize());
During(CompleteError, When(Finalize).Finalize());
// The "SetCompleted*" thing is what triggers purging of the state context info from the store (eg. Redis)... without that, the
// old completed state keys will gradually accumulate and dominate the Redis store.
SetCompletedWhenFinalized();
}
}
When debug this test, the _harness has the BeginProcessing message in its Sent collection, but there's nothing in the _saga.Created collection. It seems like I'm missing some plumbing to cause the harness to actually drive the state machine when the messages are sent?
====
Removing the .Wait() calls from SetUp() and TearDown() and updating the test to the following does NOT change the behavior:
[TestMethod]
public async Task That_Can_Start()
{
try
{
await _harness.Start();
// Arrange
// Act
await _harness.InputQueueSendEndpoint.Send(new BeginProcessing
{
ProcedureId = Guid.NewGuid(),
Steps = new List<string> {"A", "B", "C"}
});
// Assert
var sagaContext = _saga.Created.First();
sagaContext.Saga.RemainingSteps.ShouldHaveCountOf(3);
}
finally
{
await _harness.Stop();
}
}
It turns out that the test code as shown above was suffering from a race condition between the _harness.InputQueueSendEndpoint.Send operation and some asynchronous (beyond what await on the Send waits for) behavior in the StateMachineSagaTestHarness. As a result, the "Assert" phase of the test code was executing before the saga had been created and allowed to handle the sent message.
Digging into the SagaTestHarness code a bit, I found a few helper methods that I was able to use to wait until certain conditions on the saga are met. The methods are:
/// <summary>
/// Waits until a saga exists with the specified correlationId
/// </summary>
/// <param name="sagaId"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<Guid?> Exists(Guid sagaId, TimeSpan? timeout = null)
/// <summary>
/// Waits until at least one saga exists matching the specified filter
/// </summary>
/// <param name="filter"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<IList<Guid>> Match(Expression<Func<TSaga, bool>> filter, TimeSpan? timeout = null)
/// <summary>
/// Waits until the saga matching the specified correlationId does NOT exist
/// </summary>
/// <param name="sagaId"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<Guid?> NotExists(Guid sagaId, TimeSpan? timeout = null)
So I settled on using things like await _saga.Match(s => null != s.RemainingSteps); and such to effectively duplicate my later asserts and wait either until the timeout (default is 30 seconds) or the later-asserted condition has become true (and therefore safe to Assert against).. whichever comes first.
This will get me unstuck for now until I can think of a better way to know when the harness is "caught up" and ready to be interrogated.

Mock ImongoCollection driver 2.0.1 C#

I'm trying to write unit test for my repository layer that is using MongoDb 2.0.1 driver.
I have a dependency of ImongoCollection.
Someone knows how to mock that avoiding to use real db?
Thank you
Following is the code:
public class Repository<T> : IRepository<T>
where T : Content
{
private readonly IMongoCollection<T> _mongoCollection;
public Repository(IMongoCollection<T> mongoCollection)
{
_mongoCollection = mongoCollection;
}
public void Insert(T entity)
{
_mongoCollection.InsertOneAsync(entity);
}
public async void Update(T entity)
{
await _mongoCollection.InsertOneAsync(entity);
}
public async void Delete(string id)
{
await _mongoCollection.DeleteOneAsync(Builders<T>.Filter.Where(x => x.Id == id));
}
public async Task<T> GetById(string id)
{
return await _mongoCollection.Find(Builders<T>.Filter.Where(x => x.Id == id)).FirstOrDefaultAsync();
}
/// <summary>
/// This method will retrieve a list of items
/// by passing a dynamic mongo query
/// Eg. AND - var filter = builder.Eq("cuisine", "Italian") & builder.Eq("address.zipcode", "10075");
/// Eg. OR - var filter = builder.Eq("cuisine", "Italian") | builder.Eq("address.zipcode", "10075");
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public async Task<List<T>> GetByFilter(FilterDefinition<T> filter)
{
filter = filter ?? new BsonDocument();
return await _mongoCollection.Find(filter).ToListAsync();
}
public async Task<List<T>> GetAll()
{
return await _mongoCollection.Find(new BsonDocument()).ToListAsync();
}
}
I'm trying someting like
private Mock<IMongoCollection<Content>> _mockContentCollection;
private IRepository<Content> _contentRepository;
[SetUp]
public void TestMethod1()
{
_mockContentCollection = new Mock<IMongoCollection<Content>>();
_mockContentCollection.Setup(x => x.)....
_contentRepository = new Repository<Content>(_mockContentCollection.Object);
}
[Test]
public void GetAll_With_Results()
{
var a = _contentRepository.GetAll();
}

Automocking Web Api 2 controller

I am trying to auto mock ApiController class in my test cases. It worked perfectly when I was using WebApi1. I started to use WebApi2 on the new project and I am getting this exception thrown after I try to run my new tests:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Security.Cryptography.CryptographicException: pCertContext is an invalid handle.
at System.Security.Cryptography.CAPI.CertSetCertificateContextProperty(SafeCertContextHandle pCertContext, UInt32 dwPropId, UInt32 dwFlags, SafeLocalAllocHandle safeLocalAllocHandle)
at System.Security.Cryptography.X509Certificates.X509Certificate2.set_Archived(Boolean value)
My test code:
[Theory, AutoMoqData]
public void approparte_status_code_is_returned(
string privateKey,
UsersController sut)
{
var response = sut.GetUser(privateKey);
var result = response;
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
Test case does work if I create sut manually:
[Theory, AutoMoqData]
public void approparte_status_code_is_returned(
string privateKey,
[Frozen]Mock<IUserModel> stubModel)
{
var sut = new UsersController(stubModel.Object);
var response = sut.GetUser(privateKey);
var result = response;
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
It's seems that something goes wrong when trying to mock the ControllerContext.RequestContext.ClientCertificate I've tried to create a fixture without it (using AutoFixture .Without() method) but then even the old tests started to fail.
My AutoMoqDataAttribute:
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute()
: base(new Fixture()
.Customize(new WebApiCustomization()))
{
}
}
WebApi customization:
public class WebApiCustomization : CompositeCustomization
{
public WebApiCustomization()
: base(
new HttpRequestMessageCustomization(),
new AutoMoqCustomization())
{
}
}
HttpRequestMessage customization:
public class HttpRequestMessageCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x =>
{
x.Properties[HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();
})
);
}
}
UsersController:
/// <summary>
/// Handles user's account.
/// </summary>
[RoutePrefix("api/v1/users/{privateKey:length(64)}")]
public class UsersController : ApiController
{
private readonly IUserModel _model;
public UsersController(IUserModel model)
{
_model = model;
}
/// <summary>
/// Returns a user.
/// </summary>
/// <param name="privateKey">The private key of the user.</param>
/// <returns>
/// 200 (OK) with user data is returned when user is found.
/// 404 (Not found) is returned when user is not found.
/// </returns>
[HttpGet]
[Route("")]
public HttpResponseMessage GetUser(string privateKey)
{
UserProjection projection;
try
{
projection = new UserProjection(_model.Get(privateKey));
}
catch (UserNotFoundException)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, projection);
}
}
Note:
The original answer requires the same customization to be copied for each new ApiController.
Generalized approach
An alternative way is to automatically fill the Request property on all ApiControllers (thus saving you from cut, copy, and paste):
internal class ApiControllerCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new FilteringSpecimenBuilder(
new Postprocessor(
new MethodInvoker(
new ModestConstructorQuery()),
new ApiControllerFiller()),
new ApiControllerSpecification()));
}
private class ApiControllerFiller : ISpecimenCommand
{
public void Execute(object specimen, ISpecimenContext context)
{
if (specimen == null)
throw new ArgumentNullException("specimen");
if (context == null)
throw new ArgumentNullException("context");
var target = specimen as ApiController;
if (target == null)
throw new ArgumentException(
"The specimen must be an instance of ApiController.",
"specimen");
target.Request =
(HttpRequestMessage)context.Resolve(
typeof(HttpRequestMessage));
}
}
private class ApiControllerSpecification : IRequestSpecification
{
public bool IsSatisfiedBy(object request)
{
var requestType = request as Type;
if (requestType == null)
return false;
return typeof(ApiController).IsAssignableFrom(requestType);
}
}
}
The value of type HttpRequestMessage, for the Request property, is built using the following customization:
internal class HttpRequestMessageCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
new HttpConfiguration()));
}
}
Packing everything into a composite Customization
Create a Customization composite as below - note that the order of AutoFixture Customizations matter:
internal class ApiControllerConventions : CompositeCustomization
{
internal ApiControllerConventions()
: base(
new HttpRequestMessageCustomization(),
new ApiControllerCustomization(),
new AutoMoqCustomization())
{
}
}
Hope that helps.
Note:
Assuming that the UserController class takes an IUserModel through its constructor.
As it looks like, the default constructor of ApiController performs some work (probably more than simple assignments).
If the UserController class takes an IUserModel through its constructor, you can pick that constructor (the greediest) instead.
Update:
Replace the HttpRequestMessageCustomization customization with:
internal class ApiControllerCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
new HttpConfiguration()));
fixture.Customize<UsersController>(c => c
.OmitAutoProperties()
.With(x => x.Request, fixture.Create<HttpRequestMessage>()));
}
}
And the original test will execute fine.
Based on Nikos' answer:
This is a more generic way of using this customization where the controller type can be supplied and the Customization can be used for any controller
internal class WebApiCustomization<TControllerType> : ICustomization
where TControllerType : ApiController
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
new HttpConfiguration()));
fixture.Customize<TControllerType>(c => c
.OmitAutoProperties()
.With(x => x.Request, fixture.Create<HttpRequestMessage>()));
}
}
Then use as follows:
var fixture = new Fixture().Customize(
new WebApiCustomization<UsersController>());
var sut = fixture.Create<UsersController>();

Categories