Have I written an untestable method? As the library I am using has an important method which is implemented as an extension method, it seems like I am unable to fake it. And thus, unable to test my method.
First, I'll set out a truncated version of the method I want to test.
Then, I'll set out the attempt I have made to fake it using FakeItEasy.
The method uses caching and it is the call to the static method in the caching library LazyCache which I am struggling to fake:
public async Task<BassRuleEditModel> GetBassRuleEditModel(
int facilityId,
int criteriaId,
int bassRuleId,
BassRuleEditDto bassRuleEditDto)
{
var url = _bassRuleService.GetServiceConnectionForFacility(facilityId).Url;
var dto = bassRuleEditDto ?? _bassRuleService.GetBassRuleEditDto(bassRuleId);
var bassRuleEditModel = new BassRuleEditModel
{
...
LocationList = await GetLocations(url),
...
};
...
return bassRuleEditModel;
}
private async Task<IEnumerable<SelectListItem>> GetLocations(string url)
{
var cacheKey = string.Concat(CacheKeys.Location, url);
var selectList = await _appCache.GetOrAddAsync(cacheKey, async () =>
{
return new SelectList(await _tasksAndPrioritiesService.ReturnLocationsAsync(url), NameProperty, NameProperty);
}
, CacheKeys.DefaultCacheLifetime);
return selectList;
}
It is the GetOrAddAsync method which is an extension method.
I just want the fake to return from the cache an empty SelectList.
Note, the AppCache and all dependencies are injected using constructor injection.
The unit test which I have written, where I have tried to fake the AppCache is:
[Fact]
public async Task Un_Named_Test_Does_Stuff()
{
var url = "http://somesite.com";
var referrer = new Uri(url);
var facilityId = GetRandom.Id();
var serviceConnectionDto = new ServiceConnectionDto
{
Url = "http://google.com" // this url does not matter
};
var cacheKey = string.Concat(CacheKeys.Location, serviceConnectionDto.Url);
A.CallTo(() => _bassRuleService.GetServiceConnectionForFacility(facilityId)).Returns(serviceConnectionDto);
A.CallTo(() => _urlHelper.Content("~/ServiceSpec/ListView")).Returns(url);
A.CallTo(() => _appViewService.GetReferrer(url)).Returns(referrer);
A.CallTo(() => _appCache.GetOrAddAsync(cacheKey, A<Func<Task<SelectList>>>.Ignored))
.Returns(Task.FromResult(new SelectList(Enumerable.Empty<SelectListItem>().ToList())));
var editModel = await
_bassRuleService.GetBassRuleEditModel(GetRandom.Int32(),
GetRandom.Int32(),
GetRandom.Int32(),
null
);
var path = editModel.Referrer.AbsolutePath;
editModel.Referrer.AbsolutePath.ShouldBe(referrer.AbsolutePath);
}
I create the fakes in the constructor of the test (using xUnit):
public BassRuleQueryServiceTests()
{
_currentUser = A.Fake<ICurrentUser>();
_bassRuleService = A.Fake<IBassRuleService>();
_tasksAndPrioritiesService = A.Fake<ITasksAndPrioritiesService>();
_appViewService = A.Fake<IAppViewService>();
_urlHelper = A.Fake<IUrlHelper>();
_applicationDateTime = A.Fake<IApplicationDateTime>();
_appCache = new MockCacheService();
}
The error from running the test is:
Message:
FakeItEasy.Configuration.FakeConfigurationException :
The current proxy generator can not intercept the method LazyCache.AppCacheExtenions.GetOrAddAsync1[Microsoft.AspNetCore.Mvc.Rendering.SelectList](LazyCache.IAppCache cache, System.String key,
System.Func1[System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.Rendering.SelectList]] addItemFactory) for the following reason:
- Extension methods can not be intercepted since they're static.>
I get the fact that faking a static method is not on. I'm looking for solutions.
Do I need to pressure library authors to not use extension methods? (Facetious question)
Cheers
As you have correctly noted, extensions are static methods, and static methods can't be faked.
Extension methods are often just wrappers to simplify operations on the type they extend; it appears to be the case here. The GetOrAddAsync extension method you're calling ends up calling the IAppCache.GetOrAddAsync method. So you should fake that method instead.
A.CallTo(() => _appCache.GetOrAddAsync(cacheKey, A<Func<ICacheEntry, Task<SelectList>>>.Ignored))
.Returns(new SelectList(Enumerable.Empty<SelectListItem>().ToList()));
It's not very convenient, because it means you need to know what the extension method does, but there's no way around it (short of creating an abstraction layer around the library, but LazyCache is already an abstraction around Microsoft.Extensions.Caching.Memory...)
(btw, you don't need Task.FromResult; the Returns method has an overload that accepts a T when you configure a method returning a Task<T>)
Also, if you're going to return an empty sequence anyway, you don't need to configure the method at all. The default behavior of FakeItEasy will be to return a dummy IEnumerable<SelectListItem> which is empty.
As an alternaive to the excellent answer by #Thomas Levesque, two other alternatives would be:
not to mock the cache at all - use the a real CachingService as it runs in memory and so would be perfectly reasonable to include in the tests.
Use the mock instance MockCachingService cache that ships with LazyCache for this purpose.
See https://github.com/alastairtree/LazyCache/wiki/Unit-testing-code-using-LazyCache for an example.
Related
My code makes network calls. Sometimes these fail and I've implemented some retry logic. Now I wish to test this with a unit test. I'm using Mockito for this. I'm having trouble mocking this particular method call, this is my test code:
var mock = MockHttpClient();
var answers = <Future<String>>[
Future.error(Exception('boom')),
Future.value('some return value'),
];
when(mock.getStuff()).thenAnswer((_) => answers.removeAt(0));
var client = Client(inner: mock);
var result = client.someCall(); // This method call uses the `inner` object to make the `getStuff` network call.
expect(result, 'some return value');
Running this code throws an exception. And I get that because of the first returned Future! Mockito has the appropriate method call named thenThrow but I can't figure out how to combine the two.
I've figured it out. The trick is not to use a Mock but a Fake.
That looks like this:
class FakeHttpClient extends Fake implements HttpClient {
final List<Future<List<String>>> answers = [
Future.error(Exception('boom')),
Future.value('some return value'),
];
#override
Future<String> getStuff() {
return answers.removeAt(0);
}
}
Then use an instance of FakeHttpClient as an argument to the original example's Client:
var fake = FakeHttpClient();
var client = Client(inner: fake);
var result = client.someCall(); // This method call uses the `inner` object to make the `getStuff` network call.
expect(result, 'some return value');
I'm using the MockingContainer<T> to automatically set up my dependencies. How do I assert that a property on one of those dependencies gets set?
[SetUp]
public void SetUp()
{
//arrange
_baseUrl = "http://baseUrl";
_container = new MockingContainer<ApiInteractionService>();
_container.Arrange<IConfigService>(m => m.BaseUrl).Returns(_baseUrl);
_uut = _container.Instance;
}
The following fails with 0 calls, which makes sense since I believe it's looking at the Getter, not the Setter. So how do I assert that the Setter was called by the unit under test?
[Test]
public void BaseUrlSet()
{
//act
var _ = _uut.MakeRequest((InitialRequest) Arg.AnyObject);
//assert
_container.Assert<IRestService>(m => m.BaseUrl, Occurs.Once());
}
Per the documentation (located at JustMock Docs for anyone who isn't familiar but wishes to try assisting) it appears I should be using Mock.ArrangeSet(lambda), however I cannot seem to figure out how to get that syntax to work in relation to MockingContainer<T>.
If worse comes to worse, I can just NOT use MockingContainer<T>, but I'd prefer to not have to refactor my test suite just to accommodate one specific unit test.
Not that it's really relevant to the question, but in the off chance anyone needs it, here is a stub of ApiInteractionService
public ApiInteractionService(IRestService restService, IConfigService configService)
{
_restService = restService;
_restService.BaseUrl = configService.BaseUrl;
}
public string MakeRequest(InitialRequest initialRequest)
{
return _restService.Post(initialRequest);
}
Why not simply assert that BaseUrl has the correct value at the end of the test?
var baseUrl = _container.Get<IRestService>().BaseUrl;
Assert.AreEqual(baseUrl, _baseUrl);
As suggested in the comments, _container.Assert<IRestService>(m => m.BaseUrl == _baseUrl) will not work. MockingContainer<T>.Assert asserts an expectation, it's not just asserting truth like regular asserts. The correct syntax would have been:
_container.AssertSet<IRestService>(restService => restService.BaseUrl = _baseUrl, Occurs.Once());
but, oddly, there is no AssertSet method on the container.
Scenario
I would like to check if a component (the sut) logs error in a particular condition. The ILogger interface constructor injected into the component, and the Error method has 4 overloads.
So I create a ILogger mock in the Arrange and using it in the Act.
I should not expect which overload the sut is using, just would like to expect and check if any of the overload called. (that would extremely white-box, and expects far more than the functional spec.)
Question
Currently my conclusion is that I can not utilize the .Received instead I must install callbacks for all the 4 overloads, and set a variable inside them, and in the Assert part I examine that variable.
Is any simple way to do this what I missed?
(example)
[TestMethod]
public void ShouldLogErrorIfEmailIsInvalid2()
{
// Arrange
var testEmailAddress = "dummy";
//var mock = new Mock<IEMailValidator>();
var validator = Substitute.For<IEMailValidator>();
validator.Validate(Arg.Any<string>()).Returns(false);
var logger = Substitute.For<ILogger>();
var sut = new CustomerController(validator, logger);
var customer = new Customer() { Email = testEmailAddress };
// Act
sut.Post(customer);
// Assert
// *** Here I do not want to expect a specific overload of Error, instead any of the 4 overloads satisfies the expectation
logger.Received(1).Error(Arg.Is<string>( m => m.ToLower().Contains("email")), Arg.Any<object>());
}
NSubstitute does not have built-in syntax for this, but it is possible to query all ReceivedCalls() and manually assert on this.
For example:
var errorCalls = logger.ReceivedCalls()
.Where(x => x.GetMethodInfo().Name == nameof(logger.Error))
.Where(x => (x.GetArguments()[0] as string).ToLower().Contains("email"));
Assert.AreEqual(1, errorCalls.Count());
If this this is something you need frequently you could implement some helper methods and package this up into something fairly concise I think. (Maybe static void ReceivedCallToAny(this object substitute, string methodName, Func<object[], bool> requiredArgs) with some helpers like T GetItemAs<T>(object[] items) to access arguments?)
I am currently having issues with testing a method which my controller uses which is mocked. it has a return type of an specific enum. I am currently always getting back from this mocked method the default enum value, not the value that I have specified it to return. Am i missing something? I have tried both Moq and JustMock lite with the same results. JustMock lite example below.
Hopefully i haven't made any mistakes in copying the code, I have changed all the names of the objects so apologies for that.
Here is part the unit test:
var returnStatus = ExampleEnum.Invalid;
//Mock the client
var client = Mock.Create<ITestInterface>();
Mock.Arrange(() => client.ValidateSomething(Guid.NewGuid()))
.Returns(returnStatus).MustBeCalled();
var testController = new TestController(client);
var result = testController.DoSomething(Guid.NewGuid().ToString()) as ViewResult;
Here are the relevant bits from the controller:
private ITestInterface _client { get; set; }
public TestController(ITestInterface client)
{
_client = client;
}
Here is part of my controller action:
public ActionResult DoSomething(string id)
{
Guid token;
if(!string.IsNullOrEmpty(id) && Guid.TryParse(id, out token))
{
using (var client = _client)
{
ApplicationUser applicationUser;
var status = client.ValidateSomething(token);
switch (status)
{
The client is mocked correctly but the "status" property getting returned is always ExampleEnum.DefaultValue not the value i have specified to be the result.
I hope i have provided enough information. Any help much appreciated.
You probably did your setup wrong.
Guid.NewGuid() returns a new random GUID, so the GUID you use to setup your mock and the GUID you use to call the DoSomething method will never be the same.
You should do something like:
var guid = Guid.NewGuid()
...
Mock.Arrange(() => client.ValidateSomething(guid))
.Returns(returnStatus).MustBeCalled();
...
var result = testController.DoSomething(guid.ToString()) as ViewResult;
using the same GUID for the mock and for the call to DoSomething.
I don't know about JustMock, but with Moq you could also simply use It.IsAny to match all GUIDs:
client.Setup(c => c.ValidateSomething(It.IsAny<Guid>())).Returns(returnStatus);
I'm trying to get my controller in a unit test to return a mock file when Request.Files[0] is called
From other posts on this site I've put together:
[TestMethod]
public void CreateFileInDatabase()
{
var repository = new MocRepository();
var controller = GetController(repository);
HttpContextBase mockHttpContext = MockRepository.GenerateMock<HttpContextBase>();
HttpRequestBase mockRequest = MockRepository.GenerateMock<HttpRequestBase>();
mockHttpContext.Stub(x => x.Request).Return(mockRequest);
mockRequest.Stub(x => x.HttpMethod).Return("GET");
var filesMock = MockRepository.GenerateMock<HttpFileCollectionBase>();
var fileMock = MockRepository.GenerateMock<HttpPostedFileBase>();
filesMock.Stub(x => x.Count).Return(1);
mockRequest.Stub(x => x.Files).Return(filesMock);
var t = mockHttpContext.Request;
var automobile = new Automobile{ automobileNumber = "1234" };
controller.ControllerContext = new ControllerContext(mockHttpContext, new RouteData(), controller);
controller.Create(automobile);
}
When I'm in the controller during a test and call Request.Files I get the filesMock great.
However I want to be able to call Request.Files[0] and get a mock File which I can pass as a parameter to a method.
I haven't done much mocking before so any help would be appreciated!
Your filesMock object is a mock, therefore it have no idea how to resolve Files[0]. You need to tell it what to return, if someone asks for the first file:
filesMock.Stub(x => x[0]).Return(fileMock);
Add the above line after the creation of the fileMock object and the code works :)
The easiest way is to add an abstraction over the Request.Files[0] via an interface and object that actually calls out to Request.Files[0]
Note: I'm not actually sure what the datatype is for Request.Files[0], just using IFile as an example.
public interface IFileRetriever
{
IFile GetFile();
}
public class FileRetriever
{
public IFile GetFile()
{
return Request.Files[0];
}
}
Obviously suit the interface and real implementation to your use case, it will probably not be what is above...
In your class that currently calls out to Request.Files[0] just take in the IFileRetriever as a dependency, which is straightforward to mock out in Rhino Mocks (or any mocking/faking framework)