entity framework - Using DbContext in MS unit test -
i not sure how fit ef business logic tests. let me give example of how works @ runtime (no testing, regular application run):
context.set<t>.add(instance);
when add entity using above generic method, instance added context, , ef fixes navigation properties behind scenes. example, if exists [instance.parent] property, , [parent.instances] collection property (1-to-many relationship), ef automatically add instance parent.instances collection behind scenes.
my code depends on [parent.instances] collection, , if empty, fail. when writing unit tests using ms testing framework, how can reuse power of ef, can still behind-the-scenes job, uaing memory data storage, , not actual database? not interested whether ef added, modified or deleted in database, interested in getting ef magic on in-memory sets.
i've been doing mock dbcontext , mock dbset i've created. store test data in memory , allow of standard things can on dbset.
your code acquires dbcontext have changed acquires mockdbcontext when running under unit test. can determine if running under mstest following code:
public static bool isinunittest { { return appdomain.currentdomain.getassemblies() .any(assembly => assembly.fullname.startswith( "microsoft.visualstudio.qualitytools.unittestframework")); } }
here code mockdbcontext:
using system; using system.collections.generic; using system.data.common; using system.data.entity; using system.data.entity.core.objects; using system.data.entity.infrastructure; using system.threading; using system.threading.tasks; namespace consoleapplication5 { // productiondbcontext dbcontext class // generated entity framework public class mockdbcontext: productiondbcontext { public mockdbcontext() { loadfakedata(); } // entities (for we'll provide mockdbset implementation // , test data) public override dbset<account> accounts { get; set; } public override dbset<accountgenlink> accountgenlinks { get; set; } public override dbset<accountpermit> accountpermits { get; set; } public override dbset<acctdocgenlink> acctdocgenlinks { get; set; } // dbcontext method overrides private int internalsavechanges() { // return 0 in mock return 0; } public override int savechanges() { return internalsavechanges(); } public override task<int> savechangesasync() { return task.fromresult(internalsavechanges()); } public override task<int> savechangesasync(cancellationtoken cancellationtoken) { // ignore cancellation token in mock return savechangesasync(); } private void loadfakedata() { // tables accounts = new mockdbset<account>(this); accounts.addrange(new list<account> { new account { ssn_ein = "123456789", code = "a", accttype = "cd", acctnumber = "1", pending = false, bankofficer1 = string.empty, bankofficer2 = null, branch = 0, type = "18", drm_rate_code = "18", officer_code = string.empty, open_date = new datetime(2010, 6, 8), maturity_date = new datetime(2010, 11, 8), hostacctactive = true, effectiveacctstatus = "a" }, new account { ssn_ein = "123456789", code = "a", accttype = "dd", acctnumber = "00001234", pending = false, bankofficer1 = "bck", bankofficer2 = string.empty, branch = 0, type = "05", drm_rate_code = "00", officer_code = "djt", open_date = new datetime(1998, 9, 14), maturity_date = null, hostacctactive = true, effectiveacctstatus = "a" }, new account { ssn_ein = "123456789", code = "a", accttype = "ln", acctnumber = "1", pending = false, bankofficer1 = "lmp", bankofficer2 = string.empty, branch = 0, type = "7", drm_rate_code = null, officer_code = string.empty, open_date = new datetime(2001, 10, 24), maturity_date = new datetime(2008, 5, 2), hostacctactive = true, effectiveacctstatus = "a" } }); accountgenlinks = new mockdbset<accountgenlink>(this); accountgenlinks.addrange(new list<accountgenlink> { // add test data here if needed }); accountpermits = new mockdbset<accountpermit>(this); accountpermits.addrange(new list<accountpermit> { // add test data here if needed }); acctdoclinks = new mockdbset<acctdoclink>(this); acctdoclinks.addrange(new list<acctdoclink> { new acctdoclink { id = 1, ssn_ein = "123456789", code = "a", accttype = "dd", acctnumber = "00001234", docid = 50, doctype = 5 }, new acctdoclink { id = 25, ssn_ein = "123456789", code = "6", accttype = "cd", acctnumber = "1", docid = 6750, doctype = 5 } }); } } }
and here code mockdbset:
using system; using system.collections; using system.collections.generic; using system.collections.objectmodel; using system.data.entity; using system.data.entity.core.metadata.edm; using system.data.entity.infrastructure; using system.linq; using system.linq.expressions; using system.threading; using system.threading.tasks; namespace consoleapplication5 { public sealed class mockdbset<tentity> : dbset<tentity>, iqueryable, ienumerable<tentity>, idbasyncenumerable<tentity> tentity : class { public mockdbset(mockdbcontext context) { // entity set entity // used when figure out whether generate // identity values entityset = ((iobjectcontextadapter) context).objectcontext .metadataworkspace .getitems<entitycontainer>(dataspace.sspace).first() .baseentitysets .firstordefault(item => item.name == typeof(tentity).name); data = new observablecollection<tentity>(); query = data.asqueryable(); } private observablecollection<tentity> data { get; set; } type iqueryable.elementtype { { return query.elementtype; } } private entitysetbase entityset { get; set; } expression iqueryable.expression { { return query.expression; } } ienumerator ienumerable.getenumerator() { return data.getenumerator(); } public override observablecollection<tentity> local { { return data; } } iqueryprovider iqueryable.provider { { return new mockdbasyncqueryprovider<tentity>(query.provider); } } private iqueryable query { get; set; } public override tentity add(tentity entity) { generateidentitycolumnvalues(entity); data.add(entity); return entity; } public override ienumerable<tentity> addrange(ienumerable<tentity> entities) { foreach (var entity in entities) add(entity); return entities; } public override tentity attach(tentity entity) { return add(entity); } public override tentity create() { return activator.createinstance<tentity>(); } public override tderivedentity create<tderivedentity>() { return activator.createinstance<tderivedentity>(); } public override tentity find(params object[] keyvalues) { throw new notsupportedexception(); } public override task<tentity> findasync(params object[] keyvalues) { return findasync(cancellationtoken.none, keyvalues); } public override task<tentity> findasync(cancellationtoken cancellationtoken, params object[] keyvalues) { throw new notsupportedexception(); } private void generateidentitycolumnvalues(tentity entity) { // purpose of method, called when adding row, // ensure identity column values initialized // before performing add. if making "real" entity framework // add() call, task handled data provider , // value(s) propagated entity. in case // of mock, there nothing that, have make // at-least token effort ensure columns initialized. // in sql server, identity column can of 1 of following // data types: tinyint, smallint, int, bigint, decimal (with scale of 0), // or numeric (with scale of 0); method handles integer types // (the others typically not used). foreach (var member in entityset.elementtype.members.tolist()) { if (member.isstoregeneratedidentity) { // ok, we've got live one; our thing. // // note we'll current value of column and, // if nonzero, we'll leave alone. because // test data in our mock dbcontext provides values // identity columns , many of values foreign keys // in other entities (where provide test data). don't // want disturb existing relationships defined in test data. type columndatatype = null; foreach (var metadataproperty in member.typeusage.edmtype.metadataproperties.tolist()) { if (metadataproperty.name != "primitivetypekind") continue; switch ((primitivetypekind)metadataproperty.value) { case primitivetypekind.sbyte: columndatatype = typeof(sbyte); break; case primitivetypekind.int16: columndatatype = typeof(int16); break; case primitivetypekind.int32: columndatatype = typeof(int32); break; case primitivetypekind.int64: columndatatype = typeof(int64); break; default: throw new invalidoperationexception(); } var identitycolumngetter = entity.gettype().getproperty(member.name).getgetmethod(); var identitycolumnsetter = entity.gettype().getproperty(member.name).getsetmethod(); int64 specifiedcolumnvalue = 0; switch (columndatatype.name) { case "sbyte": specifiedcolumnvalue = (sbyte)identitycolumngetter.invoke(entity, null); break; case "int16": specifiedcolumnvalue = (int16)identitycolumngetter.invoke(entity, null); break; case "int32": specifiedcolumnvalue = (int32)identitycolumngetter.invoke(entity, null); break; case "int64": specifiedcolumnvalue = (int64)identitycolumngetter.invoke(entity, null); break; } if (specifiedcolumnvalue != 0) break; int64 maxexistingcolumnvalue = 0; switch (columndatatype.name) { case "sbyte": foreach (var item in local.tolist()) maxexistingcolumnvalue = math.max(maxexistingcolumnvalue, (sbyte)identitycolumngetter.invoke(item, null)); identitycolumnsetter.invoke(entity, new object[] { (sbyte)(++maxexistingcolumnvalue) }); break; case "int16": foreach (var item in local.tolist()) maxexistingcolumnvalue = math.max(maxexistingcolumnvalue, (int16)identitycolumngetter.invoke(item, null)); identitycolumnsetter.invoke(entity, new object[] { (int16)(++maxexistingcolumnvalue) }); break; case "int32": foreach (var item in local.tolist()) maxexistingcolumnvalue = math.max(maxexistingcolumnvalue, (int32)identitycolumngetter.invoke(item, null)); identitycolumnsetter.invoke(entity, new object[] { (int32)(++maxexistingcolumnvalue) }); break; case "int64": foreach (var item in local.tolist()) maxexistingcolumnvalue = math.max(maxexistingcolumnvalue, (int64)identitycolumngetter.invoke(item, null)); identitycolumnsetter.invoke(entity, new object[] { (int64)(++maxexistingcolumnvalue) }); break; } } } } } idbasyncenumerator<tentity> idbasyncenumerable<tentity>.getasyncenumerator() { return new mockdbasyncenumerator<tentity>(data.getenumerator()); } ienumerator<tentity> ienumerable<tentity>.getenumerator() { return data.getenumerator(); } public override tentity remove(tentity entity) { data.remove(entity); return entity; } public override ienumerable<tentity> removerange(ienumerable<tentity> entities) { foreach (var entity in entities) remove(entity); return entities; } public override dbsqlquery<tentity> sqlquery(string sql, params object[] parameters) { throw new notsupportedexception(); } } internal class mockdbasyncqueryprovider<tentity> : idbasyncqueryprovider { internal mockdbasyncqueryprovider(iqueryprovider queryprovider) { queryprovider = queryprovider; } private iqueryprovider queryprovider { get; set; } public iqueryable createquery(expression expression) { return new mockdbasyncenumerable<tentity>(expression); } public iqueryable<telement> createquery<telement>(expression expression) { return new mockdbasyncenumerable<telement>(expression); } public object execute(expression expression) { return queryprovider.execute(expression); } public tresult execute<tresult>(expression expression) { return queryprovider.execute<tresult>(expression); } public task<object> executeasync(expression expression, cancellationtoken cancellationtoken) { return task.fromresult(execute(expression)); } public task<tresult> executeasync<tresult>(expression expression, cancellationtoken cancellationtoken) { return task.fromresult(execute<tresult>(expression)); } } internal class mockdbasyncenumerable<t> : enumerablequery<t>, idbasyncenumerable<t>, iqueryable<t> { public mockdbasyncenumerable(ienumerable<t> enumerable) : base(enumerable) { } public mockdbasyncenumerable(expression expression) : base(expression) { } iqueryprovider iqueryable.provider { { return new mockdbasyncqueryprovider<t>(this); } } public idbasyncenumerator<t> getasyncenumerator() { return new mockdbasyncenumerator<t>(this.asenumerable().getenumerator()); } idbasyncenumerator idbasyncenumerable.getasyncenumerator() { return getasyncenumerator(); } } internal class mockdbasyncenumerator<t> : idbasyncenumerator<t> { public mockdbasyncenumerator(ienumerator<t> enumerator) { enumerator = enumerator; } public void dispose() { enumerator.dispose(); } public t current { { return enumerator.current; } } object idbasyncenumerator.current { { return current; } } private ienumerator<t> enumerator { get; set; } public task<bool> movenextasync(cancellationtoken cancellationtoken) { return task.fromresult(enumerator.movenext()); } } }
Comments
Post a Comment