I'm trying to bring test layer to my project but I'm not getting there :( hope someone can help me.
Controller (based on Automapper mapping and Dependency Injection Container):
public virtual ActionResult SearchCategories(string keywords)
{
var result = _categoryService.SearchCategories(keywords);
var resultViewModel = Mapper.
Map<IList<SearchCategoriesDto>,
IList<SearchCategoriesViewModel>>(result);
return View(resultViewModel);
}
Service Layer:
public IList<SearchCategoriesDto> SearchCategories(String keywords)
{
// Find the keywords in the Keywords table
var keywordQuery = _keywordRepository.Query;
foreach (string keyword in splitKeywords)
{
keywordQuery = keywordQuery.Where(p => p.Name == keyword);
}
// Get the Categories from the Search
var keywordAdCategoryQuery = _keywordAdCategoryRepository.Query;
var categoryQuery = _categoryRepository.Query;
var query = from k in keywordQuery
join kac in keywordAdCategoryQuery on k.Id equals kac.Keyword_Id
join c in categoryQuery on kac.Category_Id equals c.Id
select new SearchCategoriesDto
{
Id = c.Id,
Name = c.Name,
SearchCount = keywordAdCategoryQuery
.Where(s => s.Category_Id == c.Id)
.GroupBy(p => p.Ad_Id).Count(),
ListController = c.ListController,
ListAction = c.ListAction
};
var searchResults = query.Distinct().ToList();
return searchResults;
}
Test maded but not working:
[TestMethod]
public void Home_SearchCategories_Test()
{
// Setup
var catetoryService = new CategoryService(
_categoryRepository,
_keywordRepository,
_keywordAdCategoryRepository);
// Act
var result = catetoryService.SearchCategories("audi");
// Add verifications here
Assert.IsTrue(result.Count > 0);
}
Thanks.
I am assuming you want to create an integration test for your category service, using real repositories and database. (as oposed to an unit test where you would use stub\mocks for those repositories and test the service class in isolation)
So you would have a seaparated test assembly where you will add your integration tests, for example having a class for the integration tests at the service level.
On that class you will then create instances of all the repositories and the CategoryService before running each test, on a method with the attribute [TestInitialize]. (A method with this attribute will be run by msTest before each test)
As you are also truly working with the database you would like to be sure that any resources used are disposed. For example, disposing an entity framework context. In that case you would add a method with an attribute [TestCleanup] where you will perform any cleanup logic needed. (A method with this attribute will be run by msTest after each test)
With those methods you will make sure you have a fresh service and repositories instances on each test. Then you will implement each individual integration test following the AAA pattern (Arrange, Act, Assert)
So an example of your integration test class with that single test may look like:
public class CategoryServiceIntegrationTest
{
//dependencies of your class under test
private ICategoryRepository _categoryRepository;
private IKeywordRepository _keywordRepository;
private IKeywordAdCategoryRepository _keywordAdCategoryRepository;
//your class under test
private CategoryService _categoryService;
[TestInitialize]
public void InitializeBeforeRunningATest()
{
//manually create instances of the classes implementing the repositories
//I donĀ“t know about how they are implemented but I guess
//you would need to provide the name of a connection string in the config file (in that case this should be in a config file of the test project)
//,the connection string itself
//or maybe you need to initialize an entity framework context
_categoryRepository = new CategoryRepository( /*whatever you need to provide*/);
_keywordRepository = new KeywordRepository( /*whatever you need to provide*/);
_keywordAdCategoryRepository = new KeywordAdCategoryRepository( /*whatever you need to provide*/);
//Create the class under test with all repositories dependencies
//as it is an integration test, they are your real objects and not mocks\stubs
_categoryService = new CategoryService(_categoryRepository,
_keywordRepository,
_keywordAdCategoryRepository);
}
[TestCleanup]
public void CleanDatabaseResources()
{
//just in case you need to do something like disposing an EF context object
}
[TestMethod]
public void Home_SearchCategories_Test()
{
// Arrange
var keywords = "audi";
// Act (the _categoryService instance was created in the initialize method)
var result = _categoryService.SearchCategories(keywords);
// Assert
Assert.IsTrue(result.Count > 0);
}
}
Solution to build an Integration test for a Service (in this case, Category Service), using Autofac, Automapper (not necessary in this Service but if it would be necessary, you would need to put in the TestInitialize method as you can see in the coment line in the following solution) and Entity Framework with Daniel J.G. help (thanks Daniel):
First of all I created a separated Test Project using MSTest (only because there is a lot of documentation about it).
Second you need to put the connection string for the Entity Framework where the test data is:
<connectionStrings>
<add name="DB" connectionString="Data Source=.\sqlexpress;Database=DBNAME;UID=DBUSER;pwd=DBPASSWORD;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
In the < configuration > section after the < / configSections >
Third you create the class for the test:
namespace Heelp.Tests
{
[TestClass]
public class CategoryServiceIntegrationTest
{
// Respositories dependencies
private IRepository<Category> _categoryRepository;
private IRepository<Keyword> _keywordRepository;
private IRepository<KeywordAdCategory> _keywordAdCategoryRepository;
// Service under test: Category Service
private CategoryService _categoryService;
// Context under test: HeelpDB Connection String in app.config
private HeelpDbContext db;
[TestInitialize]
public void InitializeBeforeRunningATest()
{
// IoC dependencies registrations
AutofacConfig.RegisterDependencies();
// HERE YOU CAN CALL THE AUTOMAPPER CONFIGURE METHOD
// IN MY PROJECT I USE AutoMapperConfiguration.Configure();
// IT'S LOCATED IN THE App_Start FOLDER IN THE AutoMapperConfig.cs CLASS
// CALLED FROM GLOBAL.ASAX Application_Start() METHOD
// Database context initialization
db = new HeelpDbContext();
// Repositories initialization
_categoryRepository = new Repository<Category>(db);
_keywordRepository = new Repository<Keyword>(db);
_keywordAdCategoryRepository = new Repository<KeywordAdCategory>(db);
// Service initialization
_categoryService = new CategoryService(_categoryRepository,
_keywordRepository,
_keywordAdCategoryRepository);
}
[TestCleanup]
public void CleanDatabaseResources()
{
// Release the Entity Framework Context for other tests that will create a fresh new context.
// With this method, we will make sure that we have a fresh service and repositories instances on each test.
db.Dispose();
}
[TestMethod]
public void Home_SearchCategories_Test()
{
// Arrange
var keywords = "audi";
// Act (the _categoryService instance was created in the initialize method)
var result = _categoryService.SearchCategories(keywords);
// Assert
Assert.IsTrue(result.Count > 0);
}
}
}
Now you just have to run the test to see if it passes.
To garantee integration tests, I would recomend a second database identical from the original/production database in terms of tables, but with only your test data.
This will ensure that the tests results will remain the same based on your test data.
The only drawback is that you will need to keep sincronized the tables, but you can use SQL Admin Studio Freeware Tool from Simego to achieve that.
Regards.
Related
It's better if this issue is explained with an example. I have a database table Person with an int column named [Num]. It has only a record with the initial value of Num == 0.
In my PersonAppService.cs, there are the following 2 methods
public void TestIncrementA()
{
using (var uow = _unitOfWorkManager.Begin(new UnitOfWorkOptions { IsolationLevel = IsolationLevel.RepeatableRead })
{
var person = _personRepository.Get(1);
person.Num += 1;
Thread.Sleep(3000);
uow.Complete();
}
}
public void TestIncrementB()
{
using (var uow = _unitOfWorkManager.Begin(new UnitOfWorkOptions { IsolationLevel = IsolationLevel.RepeatableRead })
{
var person = _personRepository.Get(1);
person.Num += 1;
uow.Complete();
}
}
The 2 methods are essentially the same which increment the value of the column Num by one except that the first method delays the thread.
Now in the console of a web browser, I run the following commands in quick succession.
abp.services.app.person.testIncrementA();
abp.services.app.person.testIncrementB();
I would expect the value of Num in my database to be 2 now since it's been incremented twice. However it's only 1.
It's clear the RepeatableRead UoW is not locking the row properly. I have also tried using the attribute [UnitOfWork(IsolationLevel.RepeatableRead)] to no avail.
But, if I were to set the following in the PreInitialize of a module, it works.
Configuration.UnitOfWork.IsolationLevel = IsolationLevel.RepeatableRead;
This will unfortunately force RepeatableRead app-wide. Is there something that I'm overlooking?
To set a different isolation level from the ambient unit of work, begin another with RequiresNew:
using (var uow = _unitOfWorkManager.Begin(new UnitOfWorkOptions
{
Scope = TransactionScopeOption.RequiresNew, // Add this
IsolationLevel = IsolationLevel.RepeatableRead
})
{
...
}
Explanation
From https://aspnetboilerplate.com/Pages/Documents/Unit-Of-Work:
If a unit of work method calls another unit of work method, both use the same connection & transaction. The first entered method manages the connection & transaction and then the others reuse it.
The default IsolationLevel for a unit of work is ReadUncommitted if it is not configured. ...
Conventional Unit Of Work Methods
Some methods are unit of work methods by default:
...
All Application Service methods.
...
Is there a way to dynamically add EntitySets to an ODataConventionModelBuilder.
I'm working on an OData service in .net. Some of the entities we'll be returning are coming from an external assembly. I read the the assembly just fine and get the relevant types but since those types are variables I'm not sure how to define them as entity sets.
Example:
public static void Register(HttpConfiguration config)
{
//some config house keeping here
config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
//more config housekeeping
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "SomeService";
builder.ContainerName = "DefaultContainer";
//These are the easy, available, in-house types
builder.EntitySet<Dog>("Dogs");
builder.EntitySet<Cat>("Cats");
builder.EntitySet<Horse>("Horses");
// Schema manager gets the rest of the relevant types from reading an assembly. I have them, now I just need to create entity sets for them
foreach (Type t in SchemaManager.GetEntityTypes)
{
builder.AddEntityType(t); //Great! but what if I want EntitySET ?
builder.Function(t.Name).Returns<IQueryable>(); //See if you can put correct IQueryable<Type> here.
//OR
builder.EntitySet<t>(t.Name); //exception due to using variable as type, even though variable IS a type
}
return builder.GetEdmModel();
}
Figured it out. Just add this line inside the loop:
builder.AddEntitySet(t.Name, builder.AddEntityType(t));
READ EDIT
I have a similar implementation to AsyncCrudAppService related to filtering queries. When I run tests on top of ABPs implementation of Application Services derived of AsyncCrudAppServiceBase, everything runs fine. When I do the same running on top of my custom "filters", I get the following error:
System.ObjectDisposedException : Cannot access a disposed object [...]
Object name: 'DataManagerDbContext'.
I know the solution is using IUnitOfWorkManager and calling Begin() method to define a UnitOfWork, but since I am working with AppServices, I thought there was already a UnitOfWork defined. These are my methods:
public PagedResultDto<StateDetails> GetEditorList(EditorRequestDto input)
{
var query = _stateRepository.GetAllIncluding(p => p.Country).AsQueryable();
query = ApplySupervisorFilter(query);
query = query.ApplyFiltering(input, "Name");
var totalCount = query.Count();
query = query.ApplySorting<State, int, PagedAndSortedResultRequestDto>(input);
query = query.ApplyPaging<State, int, PagedAndSortedResultRequestDto>(input);
var entities = query.ToList();
return new PagedResultDto<StateDetails>(totalCount, ObjectMapper.Map<List<StateDetails>>(entities));
}
private IQueryable<State> ApplySupervisorFilter(IQueryable<State> query)
{
if (!SettingManager.GetSettingValue<bool>(AppSettingNames.SupervisorFlag))
{
query = ApplyUncategorizedFilter(query);
}
return query;
}
private IQueryable<State> ApplyUncategorizedFilter(IQueryable<State> query)
{
return query.Where(
p => !p.CountryId.HasValue);
}
My passing test (with manual UnitOfWork):
[Fact]
public async Task GetEditorListWithouSupervisorFlag_Test()
{
using (UnitOfWorkManager.Begin())
{
await ChangeSupervisorFlag(false);
var result = _stateAppService.GetEditorList(
new EditorRequestDto
{
MaxResultCount = 10,
});
result.Items.Any(p => p.Country == null).ShouldBe(true);
}
}
Does anybody know an solution to this "issue"? It would be annoying to define a UnitOfWork for every test I perform. It also seems like I am doing something wrong
EDIT
I have solved the issue. I must use an interface for my Application Service when running tests so it is able to mock it properly
I have solved the issue. I must use an interface for my Application Service when running tests so it is able to mock it properly
I have written a C# plugin for post update of the parent record based on the multiple fields.
In this I am trying to calculate the total value in the parent entity based on the values updated in the child entity, which has rate and units fields in it. So basically, total=rate*unit. The code builds fine, but when creating a new form in dynamics crm it genetrates a Business Process Error: Unexpected exception from plug-in (Execute): Parentchild1.parentchildpluginSystem.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
Here is my code:
namespace Parentchild1
{
public class parentchildplugin : IPlugin
{
private Entity abcevent_parent;
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context =
(IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
// Get a reference to the Organization service.
IOrganizationServiceFactory factory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = factory.CreateOrganizationService(context.UserId);
if (context.InputParameters != null)
{
//entity = (Entity)context.InputParameters["Target"];
//Instead of getting entity from Target, we use the Image
Entity entity = context.PostEntityImages["PostImage"];
Money rate = (Money)entity.Attributes["abcevent_rate"];
int unit = (int)entity.Attributes["abcevent_unit"];
// EntityReference parent = (EntityReference)entity.Attributes["abcevent_parentid"];
//Multiply
// Money total = new Money(rate.Value * units);
//Set the update entity
Entity parententity = new Entity("abcevent_parent");
//parententity.Id = parent.Id;
//parententity.Attributes["abcevent_total"] = total;
// abcevent_parentid = Guid IOrganizationservice.Create(Entity parentid);
Guid parentGuid = service.Create(abcevent_parent);
EntityReference parent = (EntityReference)entity.Attributes["abcevent_parentid"];
Money total = new Money(rate.Value * unit);
//Update
//service.Update(parententity);
}
the problem you ask for:
The given key was not present in the dictionary.
It is because one key is not in the list, if you try to get an attribute like
int number = (int)entity.Attributes["random_attribute"]
This throws the error, because random_attribute it is not in the context.
You have to make sure the attribute is in the context... the best practice to this is asking for a Contains:
if (entity.Contains("random_attribute"))
This way you know you can safely access to the attribute.
Another reason may be the Image, make sure it is in the context.
Currently I'm developing an OAuth2 authorization server using DotNetOpenAuth CTP version. My authorization server is in asp.net MVC3, and it's based on the sample provided by the library. Everything works fine until the app reaches the point where the user authorizes the consumer client.
There's an action inside my OAuth controller which takes care of the authorization process, and is very similar to the equivalent action in the sample:
[Authorize, HttpPost, ValidateAntiForgeryToken]
public ActionResult AuthorizeResponse(bool isApproved)
{
var pendingRequest = this.authorizationServer.ReadAuthorizationRequest();
if (pendingRequest == null)
{
throw new HttpException((int)HttpStatusCode.BadRequest, "Missing authorization request.");
}
IDirectedProtocolMessage response;
if (isApproved)
{
var client = MvcApplication.DataContext.Clients.First(c => c.ClientIdentifier == pendingRequest.ClientIdentifier);
client.ClientAuthorizations.Add(
new ClientAuthorization
{
Scope = OAuthUtilities.JoinScopes(pendingRequest.Scope),
User = MvcApplication.LoggedInUser,
CreatedOn = DateTime.UtcNow,
});
MvcApplication.DataContext.SaveChanges();
response = this.authorizationServer.PrepareApproveAuthorizationRequest(pendingRequest, User.Identity.Name);
}
else
{
response = this.authorizationServer.PrepareRejectAuthorizationRequest(pendingRequest);
}
return this.authorizationServer.Channel.PrepareResponse(response).AsActionResult();
}
Everytime the program reaches this line:
this.authorizationServer.Channel.PrepareResponse(response).AsActionResult();
The system throws an exception which I have researched with no success. The exception is the following:
Only parameterless constructors and initializers are supported in LINQ to Entities.
The stack trace: http://pastebin.com/TibCax2t
The only thing I've done differently from the sample is that I used entity framework's code first approach, an I think the sample was done using a designer which autogenerated the entities.
Thank you in advance.
If you started from the example, the problem Andrew is talking about stays in DatabaseKeyNonceStore.cs. The exception is raised by one on these two methods:
public CryptoKey GetKey(string bucket, string handle) {
// It is critical that this lookup be case-sensitive, which can only be configured at the database.
var matches = from key in MvcApplication.DataContext.SymmetricCryptoKeys
where key.Bucket == bucket && key.Handle == handle
select new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc());
return matches.FirstOrDefault();
}
public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
return from key in MvcApplication.DataContext.SymmetricCryptoKeys
where key.Bucket == bucket
orderby key.ExpiresUtc descending
select new KeyValuePair<string, CryptoKey>(key.Handle, new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc()));
}
I've resolved moving initializations outside of the query:
public CryptoKey GetKey(string bucket, string handle) {
// It is critical that this lookup be case-sensitive, which can only be configured at the database.
var matches = from key in db.SymmetricCryptoKeys
where key.Bucket == bucket && key.Handle == handle
select key;
var match = matches.FirstOrDefault();
CryptoKey ck = new CryptoKey(match.Secret, match.ExpiresUtc.AsUtc());
return ck;
}
public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
var matches = from key in db.SymmetricCryptoKeys
where key.Bucket == bucket
orderby key.ExpiresUtc descending
select key;
List<KeyValuePair<string, CryptoKey>> en = new List<KeyValuePair<string, CryptoKey>>();
foreach (var key in matches)
en.Add(new KeyValuePair<string, CryptoKey>(key.Handle, new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc())));
return en.AsEnumerable<KeyValuePair<string,CryptoKey>>();
}
I'm not sure that this is the best way, but it works!
It looks like your ICryptoKeyStore implementation may be attempting to store CryptoKey directly, but it's not a class that is compatible with the Entity framework (due to not have a public default constructor). Instead, define your own entity class for storing the data in CryptoKey and your ICryptoKeyStore is responsible to transition between the two data types for persistence and retrieval.