I am writing unit test cases and I am successful in writing unit test case for Query. But I am failing to write unit test case for QueryMultiple.
For Query I am writing like this:
IEnumerable<ClientTestPurpose> fakeTestPurposes = new
List<ClientTestPurpose>()
{
new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name1"},
new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name2"},
new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name3"}
};
_mock.SetupDapper(x => x.Query<ClientTestPurpose>(It.IsAny<string>(), null, null, true, null, null)).Returns(fakeTestPurposes);
var result = _libraryRepository.TestPurposes(clientModal.Id);
Assert.IsNotNull(result);
Assert.AreEqual(result.Count(), fakeTestPurposes.Count());
How to write for QueryMultiple:
using (var multi = _db.QueryMultiple(spName, spParams, commandType: CommandType.StoredProcedure))
{
var totals = multi.Read<dynamic>().FirstOrDefault();
var aggregates = multi.Read<StatusModel>();
var scripts = multi.Read<LibraryItemModel>();
var runs = multi.Read<RunSummaryModel>();
var filteredTotals = multi.Read<dynamic>().FirstOrDefault();
}
Apparently you use Moq.Dapper extenstions. Here is the code of SetupDapper and SetupDapperAsync method:
public static ISetup<IDbConnection, TResult> SetupDapper<TResult>(this Mock<IDbConnection> mock, Expression<Func<IDbConnection, TResult>> expression)
{
MethodCallExpression body = expression.Body as MethodCallExpression;
if ((body != null ? body.Method.DeclaringType : (Type) null) != typeof (SqlMapper))
throw new ArgumentException("Not a Dapper method.");
string name = body.Method.Name;
if (name == "Execute")
return (ISetup<IDbConnection, TResult>) DbConnectionInterfaceMockExtensions.SetupExecute(mock);
if (name == "ExecuteScalar")
return DbConnectionInterfaceMockExtensions.SetupExecuteScalar<TResult>(mock);
if (name == "Query" || name == "QueryFirstOrDefault")
return DbConnectionInterfaceMockExtensions.SetupQuery<TResult>(mock);
throw new NotSupportedException();
}
public static ISetup<IDbConnection, Task<TResult>> SetupDapperAsync<TResult>(this Mock<IDbConnection> mock, Expression<Func<IDbConnection, Task<TResult>>> expression)
{
MethodCallExpression body = expression.Body as MethodCallExpression;
if ((body != null ? body.Method.DeclaringType : (Type) null) != typeof (SqlMapper))
throw new ArgumentException("Not a Dapper method.");
if (body.Method.Name == "QueryAsync")
return DbConnectionInterfaceMockExtensions.SetupQueryAsync<TResult>(mock);
throw new NotSupportedException();
}
As you can see Moq.Dapper supports mocking only for Execute, ExecuteScalar, Query and QueryAsync methods. Therefore you probably get NotSupportedException on trying to mock QueryMultiple. To mock DB behavior you probably need introduce another level of abstraction first, as #TrueWill said in a comments. Here is just an example of idea how it can be in your case:
[Test]
public void DoSomethingWithQueryTest()
{
// Arrange
IEnumerable<ClientTestPurpose> fakeTestPurposes = new
List<ClientTestPurpose>
{
new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name1" },
new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name2" },
new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name3" }
};
var mock = new Mock<ILibraryRepository>();
mock.Setup(x => x.TestPurposes(It.IsAny<int>())).Returns(fakeTestPurposes);
var logicService = new SomeLogicService(mock.Object);
// Act
var result = logicService.DoSomethingWithQuery(1);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(result.Count(), fakeTestPurposes.Count());
}
[Test]
public void DoSomethingWithQueryMultipleTest()
{
// Arrange
SomeAggregate fakeTestPurposes = new SomeAggregate();
var mock = new Mock<ILibraryRepository>();
mock.Setup(x => x.TestQueryMultiple()).Returns(fakeTestPurposes);
var logicService = new SomeLogicService(mock.Object);
// Act
var result = logicService.DoSomethingWithQueryMultiple();
// Assert
Assert.IsNotNull(result);
}
public interface ILibraryRepository
{
IEnumerable<ClientTestPurpose> TestPurposes(int id);
SomeAggregate TestQueryMultiple();
}
public class LibraryRepository : ILibraryRepository
{
private readonly IDbConnection _db;
public LibraryRepository(IDbConnection db)
{
_db = db ?? throw new ArgumentNullException(nameof(db));
}
public IEnumerable<ClientTestPurpose> TestPurposes(int id)
{
return _db.Query<ClientTestPurpose>("SQL here", new { id }, null, true, null, null);
}
public SomeAggregate TestQueryMultiple()
{
string spName = "SQL here";
var spParams = new { Id = 1 };
using (var multi = _db.QueryMultiple(spName, spParams, commandType: CommandType.StoredProcedure))
{
return new SomeAggregate
{
totals = multi.Read<dynamic>().FirstOrDefault(),
aggregates = multi.Read<StatusModel>(),
scripts = multi.Read<LibraryItemModel>(),
runs = multi.Read<RunSummaryModel>(),
filteredTotals = multi.Read<dynamic>().FirstOrDefault()
};
}
}
}
public class SomeAggregate
{
public IEnumerable<dynamic> totals { get; set; }
public IEnumerable<StatusModel> aggregates { get; set; }
public IEnumerable<LibraryItemModel> scripts { get; set; }
public IEnumerable<RunSummaryModel> runs { get; set; }
public IEnumerable<dynamic> filteredTotals { get; set; }
}
/// <summary>
/// Example logic server, that just returns results from repository
/// </summary>
public class SomeLogicService
{
private readonly ILibraryRepository _repo;
public SomeLogicService(ILibraryRepository repo)
{
_repo = repo;
}
public IEnumerable<ClientTestPurpose> DoSomethingWithQuery(int id)
{
return _repo.TestPurposes(id);
}
public SomeAggregate DoSomethingWithQueryMultiple()
{
return _repo.TestQueryMultiple();
}
}
The main idea is to hide all DB specific thing behind the ILibraryRepository and move all logic that you need to test to some logic server, that will receive repository as dependency. In order code in repository should be simple, obvious, contains all DB specific logic: connection, transaction, command, object-relation mapping, etc. And you don't need to cover this code with unt tests. However you do cover code of SomeLogicService with unit tests, because this is what you really need to test. You see Dapper extension method are rather low-level abstraction, that doesn't hide details of working with DB, they are just helpers. Hope it helps.
Related
I am using MediatR for command query segregation.
I want to test the command method, my command method accept a clientappsetting model as an input .here you can see my whole handler and command code :
AddClientAppSettingCommandHandler : IRequestHandler<AddClientAppSettingCommand, AddClientAppSettingResponse>
{
private readonly ICurrentUserService _userService;
private readonly IRepository<ClientAppSettings> _repository;
public AddClientAppSettingCommandHandler(ICurrentUserService userService,
IRepositoryAccessor repositoryAccessor)
{
_userService = userService;
_repository = repositoryAccessor.GetRepository<ClientAppSettings>(_userService.CustomerIsin,
reThrowException: true, type: DatabaseType.Raven);
}
public async Task<AddClientAppSettingResponse> Handle(AddClientAppSettingCommand request,
CancellationToken cancellationToken)
{
var entity = new ClientAppSettings(_userService.CustomerIsin)
{
LightTheme = request.Setting.LightTheme,
Order = request.Setting.Order,
Notch = request.Setting.Notch,
PageSize = request.Setting.PageSize,
ApplyCommissionInPortfolio = request.Setting.ApplyCommissionInPortfolio,
UseClosingPriceInPortfolioTotalValue = request.Setting.UseClosingPriceInPortfolioTotalValue,
ShowNotifications = request.Setting.ShowNotifications,
NoSleep = request.Setting.NoSleep,
NoBalance = request.Setting.NoBalance,
DataTracker = request.Setting.DataTracker,
UserStatusBarToUp = request.Setting.UserStatusBarToUp,
PortfolioBasedOnLastPositivePeriod = request.Setting.PortfolioBasedOnLastPositivePeriod,
};
var cRepository = CacheableRepository<ClientAppSettings>.From(_repository);
var result = await cRepository.AddOrUpdateAsync(entity);
if (!result.IsSucceeded)
throw new EasyException(EasyException.DATABASE_EXCEPTION, result.Error);
return AddClientAppSettingResponse.Map(entity);
}
As you can see my handler has two dependencies ICurrentUserService , IRepositoryAccessor
My problem is IRepositoryAccessor when I run the test the repository object is null .
Here is my repository interface and imp ;
public interface IRepositoryAccessor
{
IRepository<TEntity> GetRepository<TEntity>(
string shard = "public",
DatabaseType type = DatabaseType.Raven,
Type inheritedRepository = null,
bool manualDisposing = false,
bool reThrowException=false) where TEntity : BaseEntity;
void CloseSession();
}
The imp :
public sealed class RepositoryAccessor : IRepositoryAccessor, IDisposable
{
private static readonly Dictionary<Type, object> FlyweightSqlGenerator = new();
private readonly List<IDisposable> _sessions = new();
private readonly ITracer _tracer;
private readonly IConfiguration _configuration;
public RepositoryAccessor(IConfiguration configuration, ITracer tracer = null)
{
_configuration = configuration;
_tracer = tracer;
}
public void CloseSession()
{
for (int i = 0; i < _sessions.Count; i++)
{
_sessions[i].Dispose();
}
_sessions.Clear();
}
public void Dispose() => CloseSession();
public IRepository<TEntity> GetRepository<TEntity>(
string shard = "public",
DatabaseType type = DatabaseType.Raven,
Type inheritedRepository = null,
bool manualDisposing = false,
bool reThrowException = false) where TEntity : BaseEntity
{
if (type == DatabaseType.Raven)
{
return GetRavenRepository<TEntity>(inheritedRepository, shard, manualDisposing, reThrowException);
}
else if (type == DatabaseType.Redis)
{
return new RedisRepository<TEntity>();
}
return GetSQLRepository<TEntity>(inheritedRepository, manualDisposing, reThrowException);
}
}
And here is my test :
[Fact]
public async void Test1()
{
//Arange
var mediator = new Mock<IMediator>();
var userservice = new Mock<ICurrentUserService>();
var repo = new Mock<IRepositoryAccessor>();
AddClientAppSettingCommand command = new AddClientAppSettingCommand(new domain.Entities.ClientAppSettings());
AddClientAppSettingCommandHandler handler = new AddClientAppSettingCommandHandler(userservice.Object,repo.Object);
//Act
var x = await handler.Handle(command, new System.Threading.CancellationToken());
}
And when I run the test with debug mode my repository is null :
I should define IAccessor and IRepository as a mock and setting up the Irepository for IAccessor as you can see :
var repoacc = new Mock<IRepositoryAccessor>();
var repo = new Mock<domain.Interfaces.IRepository<ClientAppSettings>>();
repoacc.Setup(i => i.GetRepository<ClientAppSettings>(It.IsAny<string>(), DatabaseType.Raven, null, false, true)).Returns(repo.Object);
I am onboarding alone on an existing project that do not have any unit test. My first goal before any refactoring is to cover 100% of the code. I would like to avoid any regression.
I have read how do I mock sqlconnection or should I refactor the code? but my case as you can see below is quite different cause I need to do more than stub simply the sqlConnection. Basically, I need to mock the db. I would like to know the best approach to achieve it.
(By the way, I do not want to use any ORM such as Entity).
Below the code of the repository :
public class HotelRepository : IHotelRepository
{
private readonly IDbConnection _dbConnection;
private readonly ILogService _loggerService;
public HotelRepository(IDbConnection dbConnection, ILogService loggerService)
{
_dbConnection = dbConnection;
_loggerService = loggerService;
}
public HotelDo GetByRid(string rid)
{
return Find(rid).FirstOrDefault();
}
public List<HotelDo> Find(string text)
{
try
{
_dbConnection.Open();
var items = new List<HotelDo>();
using (var command = _dbConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "dbo.HotelSearchByRidOrName";
command.Parameters.Add(new SqlParameter("#Text", text));
using (var reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
items.Add(new HotelDo()
{
Name = SqlExtension.ReaderToStringConverter(reader["Name"]),
Id = SqlExtension.ReaderToIntConverter(reader["Id"]),
Rid = SqlExtension.ReaderToStringConverter(reader["RIDHotel"]),
IdPms = SqlExtension.ReaderToNullableIntConverter(reader["IdPms"]),
LinkResaWeb = SqlExtension.ReaderToStringConverter(reader["LinkResaWeb"]),
LinkPms = SqlExtension.ReaderToStringConverter(reader["LinkPms"]),
IdBrand = SqlExtension.ReaderToNullableIntConverter(reader["IdBrand"]) ?? 0,
IsOnline = SqlExtension.ReaderToBoolConverter(reader["IsOnline"]) ?? false,
CodeCountry = SqlExtension.ReaderToStringConverter(reader["CodeCountry"])
});
}
}
}
return items;
}
catch (Exception e)
{
var errorMessage = $"HotelRepository Find, text {text} ";
_loggerService.Trace(LogSeverity.Error, errorMessage, e);
throw new DalException() { Source = errorMessage, };
}
finally
{
_dbConnection.Close();
}
}
public List<HotelDo> GetAll()
{
try
{
_dbConnection.Open();
var items = new List<HotelDo>();
using (var command = _dbConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "dbo.HotelGetAll";
using (var reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
bool.TryParse(reader["IsOnline"].ToString(), out var isOnline);
items.Add(new HotelDo()
{
Id = SqlExtension.ReaderToIntConverter(reader["Id"]),
Rid = SqlExtension.ReaderToStringConverter(reader["RIDHotel"]),
Name = SqlExtension.ReaderToStringConverter(reader["Name"]),
CodeCountry = SqlExtension.ReaderToStringConverter(reader["CodeCountry"]),
LinkPms = SqlExtension.ReaderToStringConverter(reader["LinkPms"]),
IdPms = SqlExtension.ReaderToNullableIntConverter(reader["IdPms"]),
IdBrand = SqlExtension.ReaderToNullableIntConverter(reader["IdBrand"]) ?? 0,
LinkResaWeb = SqlExtension.ReaderToStringConverter(reader["LinkResaWeb"]),
IsOnline = isOnline
});
}
}
}
return items;
}
catch (Exception e)
{
var errorMessage = $"HotelRepository GetAllHotels";
_loggerService.Trace(LogSeverity.Error, errorMessage, e);
throw new DalException() { Source = errorMessage, };
}
finally
{
_dbConnection.Close();
}
}
}
Thank you for your help
So based on what you've got I've set up a test frame for you to follow. I've removed less important components for breviety.
Before you you jump in I just want to give my 2 cents, if you don't know how to do this sort of thing you seem to be more or less in a junior position or less experiance with C# and also found your self in a more or less mature company that doesn't care about the development deparment, since an uncovered project just get's thrown your way says you don't have alot of resources to go about to imrpove the code base.
Test only the things that have business value (can you put a price on the piece of logic if it brakes)
Delivering stuff faster will make you look better as a programmer (noone in the business gives a damn about test covarage)
Study hard, get your experiance, good reputation and don't be afraid to get the hell out of there as soon as you start getting bored.
Main
void Main()
{
var idIndex = 0;
var ids = new string[] { "1", "2" };
var mockDataReader = new Mock<IDataReader>();
mockDataReader.SetupSequence(x => x.Read()).Returns(true).Returns(true).Returns(false);
mockDataReader.SetupGet(x => x["Id"]).Returns(() => ids[idIndex]).Callback(() => idIndex++);
var mockParameters = new Mock<IDataParameterCollection>();
var mockCommand = new Mock<IDbCommand>();
mockCommand.SetupGet(x => x.Parameters).Returns(mockParameters.Object);
mockCommand.Setup(x => x.ExecuteReader(CommandBehavior.CloseConnection)).Returns(mockDataReader.Object);
var mockConnection = new Mock<IDbConnection>();
mockConnection.Setup(x => x.CreateCommand()).Returns(mockCommand.Object);
var repo = new HotelRepository(mockConnection.Object);
var result = repo.Find("search");
Assert.Equal("1", result[0].Id);
Assert.Equal("2", result[1].Id);
}
Repository
public class HotelRepository
{
private readonly IDbConnection _dbConnection;
public HotelRepository(IDbConnection dbConnection)
{
_dbConnection = dbConnection;
}
public List<Pony> Find(string text)
{
_dbConnection.Open();
var items = new List<Pony>();
using (var command = _dbConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "dbo.HotelSearchByRidOrName";
command.Parameters.Add(new SqlParameter("#Text", text));
using (var reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
items.Add(new Pony
{
Id = reader["Id"].ToString()
});
}
}
}
return items;
}
}
Just a dumb o'l pony
public class Pony {
public string Id { get; set; }
}
I am trying to unit test my service layer as advised by #NKosi Here. I am able to do the integration test successfully by implementing the actual factory implementation without mocking anything but can't do the unit test (by mocking IDbConnection and my SQL connection factory class) as Dapper query executing fails with the error 'Object not set to an instance of an object'.
My IDbConnection factory and its implementation is as follow
public interface IDbConnectionFactory
{
IDbConnection CreateConnection();
}
public class ConnectionSetings
{
public string Name { get; set; }
}
public class SqlConnectionFactory : IDbConnectionFactory
{
private readonly ConnectionSetings connectionSettings;
public SqlConnectionFactory(ConnectionSetings connectionSettings)
{
this.connectionSettings = connectionSettings;
}
public IDbConnection CreateConnection()
{
return new SqlConnection(connectionSettings.Name);
}
}
And the XUnit test is as follow
[Fact]
public void Get_RestaurantById_ReturnsRestaurant()
{
//Arrange
var connection = new Mock<IDbConnection>();
var dbConnectionFactory = new Mock<IDbConnectionFactory>();
dbConnectionFactory.Setup(x => x.CreateConnection()).Returns(connection.Object);
//Act
var result = new SqlRestaurantDataCL(dbConnectionFactory.Object).Get(1);
//Assert
result.Name.Equals("Test Name 1");
//Assert.Equal("Test Name 1", result.Name);
}
And the Service Layer is as follow
public class SqlRestaurantDataCL : IRestaurantDataCL
{
private readonly IDbConnectionFactory factory;
public SqlRestaurantDataCL(IDbConnectionFactory factory)
{
this.factory = factory;
}
public Restaurant Get(int id)
{
using (var connection = factory.CreateConnection())
{
var selectSql = #"SELECT * From Restaurants Where Id = #Id";
var restaurant = connection.QuerySingleOrDefault<Restaurant>(selectSql, new
{
id
});
return restaurant;
}
}
}
Following is the error screenshot
Following is the answer to my question if anyone is in similar situation. Before following this solution, I would suggest to read #NKosi comments above and consult #Mikhail's solution Here.
ServiceStack.OrmLite.Sqlite package added to use in memory appraoch
internal class InMemoryDatabase
{
private readonly OrmLiteConnectionFactory dbFactory = new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider);
public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();
public void Insert<T>(IEnumerable<T> items)
{
using (var db = this.OpenConnection())
{
db.CreateTableIfNotExists<T>();
foreach (var item in items)
{
db.Insert(item);
}
}
}
}
Data Access layer is as follow
public IEnumerable<Restaurant> GetAll()
{
using (var connection = factory.CreateConnection())
{
//return connection.Query<Restaurant>("Select * From [dbo].[Restaurants] Order By Name");
return connection.Query<Restaurant>("Select * From Restaurant Order By Name");
}
}
Unit test is as follow
[Fact]
public void Get_RestaurantById_ReturnsRestaurant()
{
//Arrange
var restaurants = new List<Restaurant>
{
new Restaurant { Id = 1, Name = "Test Name 1", Cuisine = CuisineType.None},
new Restaurant { Id = 2, Name = "Test Name 2", Cuisine = CuisineType.French},
new Restaurant { Id = 3, Name = "Test Name 3", Cuisine = CuisineType.German},
new Restaurant { Id = 4, Name = "Test Name 4", Cuisine = CuisineType.Italian},
new Restaurant { Id = 5, Name = "Test Name 5", Cuisine = CuisineType.None}
};
var db = new InMemoryDatabase();
db.Insert(restaurants);
var connection = new Mock<IDbConnection>();
var dbConnectionFactoryMock = new Mock<IDbConnectionFactory>();
dbConnectionFactoryMock.Setup(c => c.CreateConnection()).Returns(db.OpenConnection());
//Act
var result = new SqlRestaurantDataCL(dbConnectionFactoryMock.Object).GetAll();
//Assert
result.Should().BeEquivalentTo(restaurants);
}
I'm using both ORMLite and Dapper in a project and would like standardized on the naming conventions used by both ORMs. In order to do this I'd like to set the NamingStrategy as such:
OrmLiteConfig.DialectProvider.NamingStrategy = new OrmLiteNamingStrategyBase();
and a unit test to verify
public class BorrowerUnitTests : IDisposable
{
private readonly ServiceStackHost appHost;
public BorrowerUnitTests()
{
//Set ORMLite to work with columns like ColumnLikeThis
// OrmLiteConfig.DialectProvider.NamingStrategy = new OrmLiteNamingStrategyBase();
appHost = new BasicAppHost(typeof(BorrowerServices).Assembly)
{
ConfigureContainer = container =>
{
container.Register<IDbConnectionFactory>(c =>
new OrmLiteConnectionFactory(ConfigUtils.GetConnectionString("LoanOrigination:Default"), PostgreSqlDialect.Provider));
container.RegisterAutoWiredAs<Repository, IRepository>();
container.RegisterAutoWired<BorrowerDomainService>();
}
}
.Init();
}
public void Dispose()
{
appHost.Dispose();
}
[Fact]
public void TestPostMethod()
{
var service = appHost.Container.Resolve<BorrowerServices>();
BorrowerCreate request = new BorrowerCreate();
request.FirstName = "Orm";
request.LastName = "Lite";
request.Email = "ormlite#servicestack.net";
var response = service.Post(request);
Assert.True(response.Id > 0, "Id retured from POST cannot be zero");
}
[Fact]
public void TestGetMethod()
{
var service = appHost.Container.Resolve<BorrowerServices>();
BorrowerGet request = new BorrowerGet();
request.Id = 1;
var response = service.Get(request);
Assert.Equal("ormlite#servicestack.net", response.Email);
}
[Fact]
public void TestPutMethod()
{
var service = appHost.Container.Resolve<BorrowerServices>();
BorrowerUpdate request = new BorrowerUpdate();
request.Id = 5;
request.FirstName = "MyFirstName2";
request.LastName = "MyLastName2";
request.Email = "MyEmail#Example.com";
var response = service.Put(request);
Assert.True(response.FirstName == "MyFirstName2", "FirstName is noth equal");
}
}
No matter where I put the NamingStrategy statement, I get a exception from the DialectProvider property of the OrmLiteConfig class, "You must set the singleton 'OrmLiteConfig.DialectProvider' to use the OrmLiteWriteExtensions"
Where's the proper place to set this property?
Thank you,
Stephen
You can just assign it to the DialectProvider you're using, e.g:
PostgreSqlDialect.Provider.NamingStrategy = new OrmLiteNamingStrategyBase();
The OrmLiteConfig.DialectProvider is a singleton that can either be set manually:
OrmLiteConfig.DialectProvider = PostgreSqlDialect.Provider;
OrmLiteConfig.DialectProvider.NamingStrategy = new OrmLiteNamingStrategyBase();
Or implicitly with the new OrmLiteConnectionFactory() constructor, which to run, needs to resolved from the IOC:
container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(...));
using (var db = container.Resolve<IDbConnectionFactory>().Open())
{
OrmLiteConfig.DialectProvider.NamingStrategy = new OrmLiteNamingStrategyBase();
}
I'm new to nUnit and I've been tasked with creating unit tests for some htmlhelper extension methods.
How should I go about creating a unit test for the following method?
public static MvcHtmlString EnumDropDownListForOrderBy<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, bool orderById, string firstElement = null, object htmlAttributes = null)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumType = GetNonNullableModelType(metadata);
IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();
IEnumerable<SelectListItem> items = values.Select(value => new SelectListItem()
{
Text = value.GetAttributeFrom<DescriptionAttribute>(value.ToString()).Description,
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
});
IEnumerable<SelectListItem> itemsFiltered = items.Where(e => !string.IsNullOrEmpty(e.Text)).AsEnumerable();
itemsFiltered = itemsFiltered.OrderBy(e => (orderById ? e.Text : e.Value));
return htmlHelper.DropDownListFor(
expression,
itemsFiltered,
firstElement,
htmlAttributes
);
}
Any help would be appreciated
Below is how you write a Unit Test for this. Note that since you have not specified that you use a Mock object framework I'm going to the poor man technique, which is the hand written stubs and mocks. There is also another helper method if you are using Moq.
It is important to note that, in order to simplify the code execution I have made couple of changes to your extension method, so the test would not fail unexpectedly. Checking for any unexpected behaver is a good defensive programming practice anyway.
Back to the tests.
SUT (System Under Test)
This is how the SUT (System Under Test) looks like and supporting types looks like. (Please feel free to modify to your need accordingly)
public static class MyHtmlHelper
{
public static MvcHtmlString EnumDropDownListForOrderBy<TModel, TEnum>
(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TEnum>> expression,
bool orderById, string firstElement = null, object htmlAttributes = null,
Func<ModelMetadata> fromLambFunc = null)
{
ModelMetadata metadata =
ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumType = GetNonNullableModelType(metadata);
IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();
IEnumerable<SelectListItem> items =
values.Select(value => new SelectListItem()
{
Text = GetText(value),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
});
IEnumerable<SelectListItem> itemsFiltered =
items.Where(e => !string.IsNullOrEmpty(e.Text)).AsEnumerable();
itemsFiltered = itemsFiltered.OrderBy(e => (orderById ? e.Text : e.Value));
return htmlHelper.DropDownListFor
(expression, itemsFiltered, firstElement, htmlAttributes);
}
private static Type GetNonNullableModelType(ModelMetadata metadata) {
return typeof (SomeEnum);
}
private static string GetText<TEnum>(TEnum value) {
return value.GetAttributeFrom<DescriptionAttribute>(value.ToString()) != null
? value.GetAttributeFrom<DescriptionAttribute>(value.ToString()).Description
: string.Empty;
}
}
public static class ExtensionMethodsAttr
{
public static T GetAttributeFrom<T>(this object instance, string propertyName)
where T : Attribute
{
var attrType = typeof(T);
var property = instance.GetType().GetProperty(propertyName);
return property != null ?
(T)property.GetCustomAttributes(attrType, false).First() : default(T) ;
}
}
public enum SomeEnum { A,}
Unit Tests
[TestFixture]
public class HtmlHelperTests
{
[Test]
public void EnumDropDownListForOrderBy_InvokeDropDownListFor_ReturnsExpectedSelectItemResult()
{
//Arrange
var expected = "<select id=\"Foo\" name=\"Foo\"></select>";
var fakeHtmlHelper = CreateHtmlHelperStaticStubs
(new ViewDataDictionary(new FakeViewModel() {Foo = SomeEnum.A}));
//var fakeHtmlHelper = CreateHtmlHelperUsingMoq
(new ViewDataDictionary(new FakeViewModel(){Foo = SomeEnum.A}));
//Act
var result = fakeHtmlHelper.EnumDropDownListForOrderBy
(model => model.Foo, It.IsAny<bool>(), null, null, null);
//Assert
Assert.AreEqual(expected, result.ToString());
}
private static HtmlHelper<FakeViewModel>
CreateHtmlHelperStaticStubs(ViewDataDictionary viewData)
{
var stubControllerContext = new ControllerContext(new FakeHttpContext(), new RouteData(), new FakeController());
var stubViewContext = new ViewContext(stubControllerContext, new FakeView(),
new ViewDataDictionary(new FakeViewModel() { Foo = SomeEnum.A }),
new TempDataDictionary(), new TextMessageWriter());
var fakeViewDataContainer = new FakeViewDataContainer();
fakeViewDataContainer.ViewData = viewData;
return new HtmlHelper<FakeViewModel>(stubViewContext, fakeViewDataContainer);
}
//Moq version
private static HtmlHelper<FakeViewModel>
CreateHtmlHelperUsingMoq(ViewDataDictionary viewData)
{
var stubControllerContext = new Mock<ControllerContext>();
stubControllerContext.Setup(x => x.HttpContext).Returns(new Mock<HttpContextBase>().Object);
stubControllerContext.Setup(x => x.RouteData).Returns(new RouteData());
stubControllerContext.Setup(x => x.Controller).Returns(new Mock<ControllerBase>().Object); ;
var stubViewContext = new Mock<ViewContext>();
stubViewContext.Setup(x => x.View).Returns(new Mock<IView>().Object);
stubViewContext.Setup(x => x.ViewData).Returns(viewData);
stubViewContext.Setup(x => x.TempData).Returns(new TempDataDictionary());
var mockViewDataContainer = new Mock<IViewDataContainer>();
mockViewDataContainer.Setup(v => v.ViewData).Returns(viewData);
return new HtmlHelper<FakeViewModel>(stubViewContext.Object, mockViewDataContainer.Object);
}
}
class FakeHttpContext : HttpContextBase
{
private Dictionary<object, object> _items = new Dictionary<object, object>();
public override IDictionary Items { get { return _items; } }
}
class FakeViewDataContainer : IViewDataContainer
{
private ViewDataDictionary _viewData = new ViewDataDictionary();
public ViewDataDictionary ViewData { get { return _viewData; } set { _viewData = value; } }
}
class FakeController : Controller { }
class FakeView : IView
{
public void Render(ViewContext viewContext, System.IO.TextWriter writer)
{
throw new NotImplementedException();
}
}
public class FakeViewModel {
public SomeEnum Foo { get; set; }
}