c# - Unit testing many simple method that call internal methods/other tested methods, resulting in complex (yet similar) output -


i've got data access layer has 2 types of method getlatestx , getx. getlatestx looks this:

    public ielementtype getlatestelementtype(guid id)     {         ielementtype record = null;         using (databasesession session = createsession())         {             record = session.connection.get<elementtyperecord>(id);         }         return record;     } 

that's reasonably easy unit test.

however, getx wraps getlatest in refcount observable , emits new values in response messaging system. testing method lot more complex. want check following, rather complex behavior:

  1. you can subscribe , retrieves value database
  2. it starts listening messages
  3. subscribing again doesn't result in repeated database call
  4. when mock message system simulates message new database access called , subscriptions new versions. 1 additional database call used.
  5. unsubscribing second subscription doesn't result in system stopping listening messages.
  6. unsubscribing first subscription results in disposal of resources, , unsubscription messages.

so, i've got in single unit test, hideous. however, i'm not sure how break up. test 1, test 2 i'd have go through 1 , 2, 3 i'd still have go through steps 1, 2, 3 etc. i'd copying same giant test, having asserts in different places each time.

and code i'm testing in method:

    public iobservable<ielement> getelement(guid id)     {         return createobservablefor(getlatestelement(id), getlatestelement);     } 

it's single line, half of has been tested earlier. other half private:

    private iobservable<t> createobservablefor<t>(t entity, func<guid, t> getlatest)     {         guid id = (entity configurationentity).id;          //return subject;         return observable.create<t>(observer =>         {             // publish first value             observer.onnext(entity);              // listen internal or external update notifications messages             action<configurationmessage> callback = (message) =>             {                 // todo, check time-stamp after previous?                  // use callback latest value                 observer.onnext(getlatest(id));             };              messageservice.subscribetoconfiguration(id.tostring(), callback);              // if has been completed , stop listening messages             return disposable.create(() =>             {                 console.writeline("unsubscribing topic " + id);                 messageservice.unsubscribetoconfiguration(id.tostring(), callback);             });         }).publish().refcount();     } 

but behaves same way getx methods.

my first thought should split getlatestx interface can test separately mock - seems split data access class 2 no reason other unit tests. don't conceptually belong separate units in mind. there way of 'mocking' dependency within class? or should split them sake of testing?

in same vein, testing functionality of getx repeatedly testing logic of createobservablefor. see why should testing each api method rather internals of api (in case changes), seems so... inefficient.

how can structure unit test in better way?

example test:

    [test]     public void getelementtypetest()     {         // test data         var id = guid.newguid();         var namea = "testnamea";         var nameb = "testnameb";          // mock database         var columnnames = new[] { "id", "name" };          // data values first set of data returned, , after configuration update b returned         var datavaluesa = new list<object[]>();         datavaluesa.add(new object[] { id, namea });          var datavaluesb = new list<object[]>();         datavaluesb.add(new object[] { id, nameb });          mockdbproviderfactory = new mockdbproviderfactory()             .adddatareadercommand(columnnames, datavaluesa)             .adddatareadercommand(columnnames, datavaluesb);          // test method         iemf emf = new emf(mockmessageservice.object, new mockhistorian(), mockdbproviderfactory.object, "");          var resultobservable = emf.getelementtype(id);          // check subscription config changes has not occurred , database not accessed         mockdbproviderfactory.verify(f => f.createconnection(), times.once);         mockmessageservice.verify(ms => ms.subscribetoconfiguration(it.isany<string>(), it.isany<action<configurationmessage>>()), times.never);          //subscribe observable         int sub1count = 0;         var subscription = resultobservable.subscribe(result => {             sub1count++;              // check result             assert.areequal(new elementtyperecord(id, (sub1count == 1 ? namea : nameb)), result, "result emf not match data");          });           // check subscribed config changes , subscription called         assert.istrue(sub1count == 1, "subscription not called");         mockmessageservice.verify(ms => ms.subscribetoconfiguration(it.isany<string>(), it.isany<action<configurationmessage>>()), times.once);          // check we've subscribed our id         assert.areequal(this.configcallbacks[0].item1, id.tostring(), "unexpected message system subscription topic");          // open second, short term subscription , ensure system not re-subscribe updates, or read data again         int sub2count = 0;         resultobservable.take(1).subscribe(result => {             sub2count++;              // check result (should second data item)             assert.areequal(new elementtyperecord(id, nameb), result, "result emf not match data");         });          // check subscribed config changes has not changed         mockmessageservice.verify(ms => ms.subscribetoconfiguration(it.isany<string>(), it.isany<action<configurationmessage>>()), times.once);          //emit new value simulating configuration change message         this.configcallbacks[0].item2(new configurationmessage(datetime.now));          // check subscriptions called         assert.istrue(sub1count == 2, "subscription not called");         assert.istrue(sub2count == 1, "subscription not called");          // unsubscribe         mockmessageservice.verify(ms => ms.unsubscribetoconfiguration(it.isany<string>(), it.isany<action<configurationmessage>>()), times.never);         subscription.dispose();          // verify subscription removed         mockmessageservice.verify(ms => ms.unsubscribetoconfiguration(it.isany<string>(), it.isany<action<configurationmessage>>()), times.once);         assert.istrue(this.configcallbacks.count == 0, "unexpected message system unsubscription topic");          // validate connection, command , reader used correctly         mockdbproviderfactory.verify(f => f.createconnection(), times.exactly(2));         mockdbproviderfactory.mockconnection.verify(c => c.open(), times.exactly(2));         mockdbproviderfactory.mockconnection.verify(c => c.close(), times.exactly(2));          //first data call         mockdbproviderfactory.mockcommands[0].verify(c => c.publicexecutedbdatareader(it.isany<commandbehavior>()), times.once);         mockdbproviderfactory.mockcommands[0].mockdatareader.verify(dr => dr.read(), times.exactly(2));          //second data call         mockdbproviderfactory.mockcommands[1].verify(c => c.publicexecutedbdatareader(it.isany<commandbehavior>()), times.once);         mockdbproviderfactory.mockcommands[1].mockdatareader.verify(dr => dr.read(), times.exactly(2));     } 


Comments

Popular posts from this blog

Command prompt result in label. Python 2.7 -

javascript - How do I use URL parameters to change link href on page? -

amazon web services - AWS Route53 Trying To Get Site To Resolve To www -