admin管理员组文章数量:1390775
I'm working on a unit test for a controller that calls a handler to retrieve a list of consumers multiple times.
The handler calls GetItemsAsync()
and returns either a null value or a list of consumers.
I'm having an issue with the Setup()
in my unit test. I'm not sure how to distinguish the two calls to the same handler returning different values. The only difference between the two calls are the values in the parameters provided to the specification for the select from the database.
I tried the following SetupSequence()
statement, but then I found that the entire sequence is returned with each call. I need a solution where I can return one value per call.
_mockConsumerRepo.SetupSequence(m => m.GetItemsAsync(It.IsAny<ISpecification<Consumer>>(), It.IsAny<string>(), It.IsAny<int?>()))
.ReturnsAsync((GetItemsResult<IEnumerable<Consumer>>)null)
.ReturnsAsync(new GetItemsResult<IEnumerable<Consumer>>
{
Results = consumers
});
I realize that if I define 2 Setups (below) for the same method, it will take the latest, so that is not an option.
_mockConsumerRepo.Setup(m => m.GetItemsAsync(It.IsAny<ISpecification<Consumer>>(), It.IsAny<string>(), It.IsAny<int?>()))
.ReturnsAsync((GetItemsResult<IEnumerable<Consumer>>)null);
_mockConsumerRepo.Setup(m => m.GetItemsAsync(It.IsAny<ISpecification<Consumer>>(), It.IsAny<string>(), It.IsAny<int?>()))
.ReturnsAsync(new GetItemsResult<IEnumerable<Consumer>>
{
Results = consumers
});
This is the call to the handler in the controller that is being executed, once with email and brand/region, and again later in the code with phone and brand/region.
var query = new GetConsumerByParametersQuery
{
EmailAddress = dto.EmailAddress,
EnrollmentBrandRegion = dto.EnrollmentBrandRegion
};
var data = await _consumerParametersQueryHandler.HandleAsync(query);
This is the code for the handler that retrieves the consumer records:
namespace cscentconsumerapi.Common.RequestHandlers
{
public class GetConsumerByParametersQuery
{
private static readonly int DEFAULT_LIMIT = 20;
private static readonly int MAX_LIMIT = 40;
private int _limit;
public string Brand { get; set; }
public string EnrollmentBrandRegion { get; set; }
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
/// <summary>
/// The token when continuating a query
/// </summary>
public string NextLink { get; set; }
/// <summary>
/// The number of purchase orders to return
/// Default value is 20, max value is 40
/// </summary>
public int Limit
{
get { return (_limit > 0 && _limit < MAX_LIMIT) ? _limit : DEFAULT_LIMIT; }
set { _limit = (int)value; }
}
}
public class GetConsumerByParametersHandler : IRequestHandler<GetConsumerByParametersQuery, (IEnumerable<ConsumerDto> consumers, string token)>
{
private readonly IModelMasterRepository<Consumer> _masterRepository;
private readonly IModelMasterRepository<SmsConsentDocument> _smsConsentRepository;
private readonly IMapper _mapper;
private readonly IDistributedCacheWrapper _cacheWrapper;
public GetConsumerByParametersHandler(
IModelMasterRepository<Consumer> masterRepository,
IModelMasterRepository<SmsConsentDocument> smsConsentRepository,
IMapper mapper,
IDistributedCacheWrapper distributedCacheWrapper)
{
_masterRepository = masterRepository;
_smsConsentRepository = smsConsentRepository;
_mapper = mapper;
_cacheWrapper = distributedCacheWrapper;
}
public async Task<(IEnumerable<ConsumerDto> consumers, string token)> HandleAsync(GetConsumerByParametersQuery request)
{
var normalizedPhone = ConsumerProfile.ParsePhoneNumber(request.EnrollmentBrandRegion, request.PhoneNumber);
var specification = new GetConsumerByFilterSpecification(request.Brand, request.EnrollmentBrandRegion, request.EmailAddress, normalizedPhone);
// Get token from memory cache
string? token;
if (!string.IsNullOrEmpty(request.NextLink))
token = await _cacheWrapper.GetStringAsync(request.NextLink);
else
token = null;
IEnumerable<ConsumerDto> consumerDtos = Enumerable.Empty<ConsumerDto>();
var response = await _masterRepository.GetItemsAsync(specification, token, request.Limit);
if (response == null)
return (consumerDtos, token);
consumerDtos = response.Results.Select(r => _mapper.Map<Consumer, ConsumerDto>(r));
consumerDtos = await RequestHandlerHelper.HydrateSmsConsentsAsync(consumerDtos.ToList(), response.Results, _smsConsentRepository, _mapper);
// Insert the new token
string newContinuationToken;
if (!string.IsNullOrEmpty(response.ContinuationToken))
newContinuationToken = await _cacheWrapper.SetStringAsync(response.ContinuationToken);
else
newContinuationToken = null;
return (consumerDtos, newContinuationToken);
}
}
}
This is the specification that is being called within the handler to select consumers.
using Ardalis.Specification;
using cscentconsumer.domain.Models.Consumer;
namespace CscEntConsumer.Infrastructure.Specifications
{
public class GetConsumerByFilterSpecification : Specification<Consumer>
{
public enum OrderBy
{
ASC,
DESC
}
public GetConsumerByFilterSpecification(
string brand = null,
string siteId = null,
string email = null,
string phoneNumber = null,
OrderBy? order = null
)
{
if (brand != null)
Query.Where(x => x.Brand == brand);
if (siteId != null)
Query.Where(x => x.EnrollmentBrandRegion == siteId);
if (email != null)
Query.Where(x => x.EmailAddress.ToLower() == email.ToLower());
if (phoneNumber != null)
{
Query.Where(x => x.PhoneNumberSearch == phoneNumber);
}
if (order != null)
{
switch (order)
{
case OrderBy.DESC:
Query.OrderByDescending(x => x.UpdateDateTime);
break;
case OrderBy.ASC:
Query.OrderBy(x => x.UpdateDateTime);
break;
}
}
else
{
Query.OrderByDescending(x => x.UpdateDateTime);
}
}
}
}
Is there a way I can distinguish the two calls when they call the exact same handler with the same parameter (query, just different values)? Any suggestions would be greatly appreciated.
I'm working on a unit test for a controller that calls a handler to retrieve a list of consumers multiple times.
The handler calls GetItemsAsync()
and returns either a null value or a list of consumers.
I'm having an issue with the Setup()
in my unit test. I'm not sure how to distinguish the two calls to the same handler returning different values. The only difference between the two calls are the values in the parameters provided to the specification for the select from the database.
I tried the following SetupSequence()
statement, but then I found that the entire sequence is returned with each call. I need a solution where I can return one value per call.
_mockConsumerRepo.SetupSequence(m => m.GetItemsAsync(It.IsAny<ISpecification<Consumer>>(), It.IsAny<string>(), It.IsAny<int?>()))
.ReturnsAsync((GetItemsResult<IEnumerable<Consumer>>)null)
.ReturnsAsync(new GetItemsResult<IEnumerable<Consumer>>
{
Results = consumers
});
I realize that if I define 2 Setups (below) for the same method, it will take the latest, so that is not an option.
_mockConsumerRepo.Setup(m => m.GetItemsAsync(It.IsAny<ISpecification<Consumer>>(), It.IsAny<string>(), It.IsAny<int?>()))
.ReturnsAsync((GetItemsResult<IEnumerable<Consumer>>)null);
_mockConsumerRepo.Setup(m => m.GetItemsAsync(It.IsAny<ISpecification<Consumer>>(), It.IsAny<string>(), It.IsAny<int?>()))
.ReturnsAsync(new GetItemsResult<IEnumerable<Consumer>>
{
Results = consumers
});
This is the call to the handler in the controller that is being executed, once with email and brand/region, and again later in the code with phone and brand/region.
var query = new GetConsumerByParametersQuery
{
EmailAddress = dto.EmailAddress,
EnrollmentBrandRegion = dto.EnrollmentBrandRegion
};
var data = await _consumerParametersQueryHandler.HandleAsync(query);
This is the code for the handler that retrieves the consumer records:
namespace cscentconsumerapi.Common.RequestHandlers
{
public class GetConsumerByParametersQuery
{
private static readonly int DEFAULT_LIMIT = 20;
private static readonly int MAX_LIMIT = 40;
private int _limit;
public string Brand { get; set; }
public string EnrollmentBrandRegion { get; set; }
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
/// <summary>
/// The token when continuating a query
/// </summary>
public string NextLink { get; set; }
/// <summary>
/// The number of purchase orders to return
/// Default value is 20, max value is 40
/// </summary>
public int Limit
{
get { return (_limit > 0 && _limit < MAX_LIMIT) ? _limit : DEFAULT_LIMIT; }
set { _limit = (int)value; }
}
}
public class GetConsumerByParametersHandler : IRequestHandler<GetConsumerByParametersQuery, (IEnumerable<ConsumerDto> consumers, string token)>
{
private readonly IModelMasterRepository<Consumer> _masterRepository;
private readonly IModelMasterRepository<SmsConsentDocument> _smsConsentRepository;
private readonly IMapper _mapper;
private readonly IDistributedCacheWrapper _cacheWrapper;
public GetConsumerByParametersHandler(
IModelMasterRepository<Consumer> masterRepository,
IModelMasterRepository<SmsConsentDocument> smsConsentRepository,
IMapper mapper,
IDistributedCacheWrapper distributedCacheWrapper)
{
_masterRepository = masterRepository;
_smsConsentRepository = smsConsentRepository;
_mapper = mapper;
_cacheWrapper = distributedCacheWrapper;
}
public async Task<(IEnumerable<ConsumerDto> consumers, string token)> HandleAsync(GetConsumerByParametersQuery request)
{
var normalizedPhone = ConsumerProfile.ParsePhoneNumber(request.EnrollmentBrandRegion, request.PhoneNumber);
var specification = new GetConsumerByFilterSpecification(request.Brand, request.EnrollmentBrandRegion, request.EmailAddress, normalizedPhone);
// Get token from memory cache
string? token;
if (!string.IsNullOrEmpty(request.NextLink))
token = await _cacheWrapper.GetStringAsync(request.NextLink);
else
token = null;
IEnumerable<ConsumerDto> consumerDtos = Enumerable.Empty<ConsumerDto>();
var response = await _masterRepository.GetItemsAsync(specification, token, request.Limit);
if (response == null)
return (consumerDtos, token);
consumerDtos = response.Results.Select(r => _mapper.Map<Consumer, ConsumerDto>(r));
consumerDtos = await RequestHandlerHelper.HydrateSmsConsentsAsync(consumerDtos.ToList(), response.Results, _smsConsentRepository, _mapper);
// Insert the new token
string newContinuationToken;
if (!string.IsNullOrEmpty(response.ContinuationToken))
newContinuationToken = await _cacheWrapper.SetStringAsync(response.ContinuationToken);
else
newContinuationToken = null;
return (consumerDtos, newContinuationToken);
}
}
}
This is the specification that is being called within the handler to select consumers.
using Ardalis.Specification;
using cscentconsumer.domain.Models.Consumer;
namespace CscEntConsumer.Infrastructure.Specifications
{
public class GetConsumerByFilterSpecification : Specification<Consumer>
{
public enum OrderBy
{
ASC,
DESC
}
public GetConsumerByFilterSpecification(
string brand = null,
string siteId = null,
string email = null,
string phoneNumber = null,
OrderBy? order = null
)
{
if (brand != null)
Query.Where(x => x.Brand == brand);
if (siteId != null)
Query.Where(x => x.EnrollmentBrandRegion == siteId);
if (email != null)
Query.Where(x => x.EmailAddress.ToLower() == email.ToLower());
if (phoneNumber != null)
{
Query.Where(x => x.PhoneNumberSearch == phoneNumber);
}
if (order != null)
{
switch (order)
{
case OrderBy.DESC:
Query.OrderByDescending(x => x.UpdateDateTime);
break;
case OrderBy.ASC:
Query.OrderBy(x => x.UpdateDateTime);
break;
}
}
else
{
Query.OrderByDescending(x => x.UpdateDateTime);
}
}
}
}
Is there a way I can distinguish the two calls when they call the exact same handler with the same parameter (query, just different values)? Any suggestions would be greatly appreciated.
Share Improve this question edited Mar 14 at 21:35 ChrisP asked Mar 13 at 23:02 ChrisPChrisP 479 bronze badges1 Answer
Reset to default 0All of the attempts described in the OP use It.IsAny<T>
, so of course Moq can't distinguish one method invocation from another. Using It.IsAny<T>
is like defining a predicate that always returns true.
Try doing separate Setups
that Moq can distinguish:
_mockConsumerRepo.Setup(m => m.GetItemsAsync(
new GetConsumerByParametersQuery
{
EmailAddress = "foo",
EnrollmentBrandRegion = "bar"
},
It.IsAny<string>(),
It.IsAny<int?>()))
.ReturnsAsync((GetItemsResult<IEnumerable<Consumer>>)null);
_mockConsumerRepo.Setup(m => m.GetItemsAsync(
new GetConsumerByParametersQuery
{
Phone = "1234",
EnrollmentBrandRegion = "baz"
},
It.IsAny<string>(),
It.IsAny<int?>()))
.ReturnsAsync(new GetItemsResult<IEnumerable<Consumer>>
{
Results = consumers
});
You may also want to replace the other It.IsAny<string>()
and It.IsAny<int?>()
with more specific values.
If, however, GetConsumerByParametersQuery
has reference equality (which objects in C# do by default), this isn't going to work, because the GetConsumerByParametersQuery
objects created in the test aren't the same objects used by the System Under Test.
If so, consider making GetConsumerByParametersQuery
immutable so that you can give it structural equality. The easiest way to do this in C# is to make it a record
.
If this is not possible, you can use It.Is<T>
to define custom predicates. You may also consider the Resemblance idiom, but only use this if all else fails.
本文标签: unit testingReturning different values with multiple calls to a method in MOQStack Overflow
版权声明:本文标题:unit testing - Returning different values with multiple calls to a method in MOQ - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744680696a2619388.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论