BrandGhost

Autofac - Testing our Autofac Wire Tap

This video is part 2 of this series. You can see part 1 here: https://youtu.be/hs_q-aHayAM In this video I discuss how writing out coded tests helped get a handle on how the functionality I wanted to achieve with wire tapping. I was able to use the API as my callers would and use scenarios to regression test against as I changed expected behaviour. My Social: LinkedIn: https://www.linkedin.com/in/nickcosentino Blog: http://www.devleader.ca/ GitHub: https://github.com/ncosentino/ Twitch: https:...
View Transcript
that i'm putting together regarding this uh idea of something called wiretapping in artifact um i saw the name online for trying to implement a feature like this so i've kind of just assumed it um so i'm gonna put a link to the original video so you can click through if you didn't see it already but just to touch on what wiretapping actually is and why this came up in the first place so autofaq is a dependency injection framework and i had something proposed to me that was essentially if we don't really have control over the core application and we want to be able to inject our own implementation of a service but also have that new service consume the originally registered service um how can we do that so essentially you're doing like a man in the middle or you know wiretap um so i put together an example of a couple of things to try it out and had it working um but as i started to do it i was i was essentially coming up with a few things that um i started to question like what you know someone using this what's the expectation around the api what feels good what feel like you know like i don't even know um just because it's you know working doesn't necessarily mean that it feels good to use if it's obvious to use so i wanted to make this video about um sort of the next step i took after getting some functionality in place um and then how how personally i like to use um in a situation like this i like to use unit tests so x unit mock um and other things i don't even think i ended up using mock for this but essentially it lets me put together a few different scenarios actually use the code and then um and then have some expectations around the behavior and then when i do that as i tweak things i can say like this test still passes which is good i didn't break the behavior and it still kind of looks the way i want to write the the calling code and i can keep adding more and more scenarios to it and seeing like what feels good and bad so that's what we're going to talk about today um so i've gone ahead and i moved over a bunch of the code i was working with um in the past video from like this sample console project into my own sort of library project that i have some unit tests and stuff with so um there sorry you couldn't see my cursor um so i have yeah essentially um a main project that has some like auto fact extensions that i want to keep building up um so that i can use those on my own projects maybe i'll release them and other people can try them out and then i have a test project down below which is where all my unit tests and uh functional tests and stuff are going to go oops didn't mean to open that sorry um so i want to talk about sort of the first steps i started taking once i had something that felt like it was kind of working um so the two the two main things that came up um in the last video and kind of walking through some stuff because there were bugs in the code i was demonstrating um and they're i guess they're bugs in the sense that they didn't have like the the functionality that was present isn't necessarily what i would have expected you could argue you know maybe that's totally what someone would have expected but i i don't personally feel so um so i wanted to kind of come up with some of these scenarios and then uh and then start iterating on them so i've gone ahead maybe i can zoom in a little bit here um i've gone ahead and put together a bunch of these x unit tests if you're not familiar with xunit essentially i can mark a text a test method um with a fact there's other things you can do but fact is sort of like your basic unit of a test um and what i've done most of these tests are very similar so just kind of walk through the the structure of it um because i am making extension methods for a container builder uh in auto fact i have to make a new container builder um i'm going to end up this is sort of the method that's being um sorry this is like a setup part so i have a really simple client i don't even think this class does anything but it's essentially a class that meets interface which is going to the i test interface so i register it a particular way if you remember in the previous video again if you haven't seen it like click the link and go watch that first it'll make more sense but i was talking about different ways that i could register these so this one is marked as single instance and the idea was that uh then i can call my new extension method which will perform the wire tap and the wire tab says when someone goes to resolve i test interface we want to run this this function so this function will give us the original sort of base instance as it's been called here and we're going to return a new um a new uh class um in the other video that i put together um i don't need to to do it in these coded tests but we were able to take the base instance and pass it into this class um i don't need to do that in my actual test i'm actually just doing a little assert here that that checks is this base instance actually um you know the underlying type that we expected so are we about to wiretap the thing we expect so that's kind of cool and then once that wiretap and the registration setup this is sort of standard auto fax stuff so we have a container builder we're going to build the dependency container and then it's pretty common practice that you make a sort of a lifetime scope for that container i'm not going to get into details about lifetime scopes and scopes and everything else but essentially it's uh it's just a way that you can literally as the name implies you can scope your resolutions of services um so uh the bulk of the tests and the assertion is sort of in here so in this particular test um and i've named it a way that sort of suggests the um the method that's being tested so it's just wiretap and i've called it default wiretap um so naming conventions for tests people have their own things i like to do uh method under test then an underscore then sort of the um the parameters of the configuration of the test then another underscore and then sort of your expected result um that's generally what i like to do so the fact that this says default wiretap in front of it um feels kind of weird because the method under test is wiretap i put default here because um because there were no other parameters in this case it's actually completely omitted there's other tests that have some other stuff here so those ones are not called default they'll be called something else but anyway that's what the default means but we do a single wiretap right so that's what the single tap means because we have a single wiretap it says the base is a single instance because you can see that right here and then the last part of this as we expect the same wiretap instances come out so in this particular test we actually try to resolve i test interface twice and if we weren't doing any wiretapping at all what we would expect because autofaq has marked this as single instance is that instance one and instance two will be the exact same instance it's not just the same type it's it's literally the same reference um so we'd be able to test that however we're wiretapping it and one of the things i was that experimenting with was maybe it would be really nice to just have sort of a default expectation of um you know you're about to wire tap something it happens to be marked as single instance your new wiretap maybe should be single instance as well by default right so you don't have to explicitly say new instance or single instance you don't have to know anything about the underlying registration just kind of like it does what you expect and that's really the interesting question is like is this what we expect i don't know so i wanted to play around with it and put some tests together that's kind of what this whole thing's about so um in the end this did actually feel good to me so i would resolve two instances i just do a quick check to make sure that the instances that we get back they're not null and they're actually sort of the resulting wiretapped type right so the wire tab did work that's sort of what these four lines are checking for us and then the interesting part is this last line is that we're going to check that instance 1 and instance 2 are the exact same reference now that happens because i made the default behavior when your base registration is single instance that your wiretapped instances will also be sort of registered as single instance behind the scene um so that was like one example of a test i made i'm like okay that feels pretty good i might not go into as much detail on all of these but um then you can see like the next test below it's almost the exact same but you can see already the name is different it says single explicit wire tab right so remember i was talking about the first i'm going to scroll up so i can show both parts but this single uh instance for wiretap is passed as a parameter whereas this other one does not have any parameter here right the default is no parameter and it will just do what the base one expects sort of the base with single instance you're going to get single instance if it was not you will not so that's what this test proves it actually um the rest of the test is identical and the reason why is the default behavior behind the scenes we'll end up sort of figuring out that because this registration here with single instance like when we explicitly say it the the implicit version actually figures out that this parameter should be single instance so these two tests that i've just gone through look almost identical um which again to me that's actually what i expected so once i tr once i put the test in place i was like this is starting to feel um good um that wasn't the case the whole way through so we're going to see some examples of where that kind of falls apart and here's the first example of that so um the way what's interesting about this one i guess is um i'm talking about another explicit parameter for the wiretap so this one was a single instance being explicit and this one is we're going to say that we want new instances like multiple instances every time someone goes to resolve this type we should make a new instance but the base registration is single instance so what i found was that based on how the registration like how i made my function um it turns out that the sort of the wiretapping mechanism never executes a second time if this is marked as single instance so what that means is that once i do a resolution and i wire tap once if this base registration is marked a single instance the next time we go to resolve the type um we are essentially guaranteed that we're just going to get the the same instance back so to me i was like oh no like that that's not what i expected i would have thought that maybe i could share the same base instance and then let my callers um be able to say every time someone resolves this share the same base instance but let's keep making new instances of our new type right like why not have that configurability i can't think of a reason off the top of my head but but why not allow it so uh unfortunately like i mentioned the way i have the wire tab set up in the code behind doesn't actually work that way so i said okay well it's not to say this is impossible to do but i said what i'm going to do is actually make it so that when someone tries to do this the expected behaviors that we tell them you can't right so i i make it throw uh an exception um i check that this message is what we expect to get up and essentially it's telling you the base type is registered a single instance you cannot make your wiretap multi-instance because of this so that's now the expected behavior um it's not what i wanted um as i mentioned it's not what i wanted but i figured okay let me put this in place for now i have a test that proves this is the current expected behavior if i have time after i can maybe come back and revisit this and say okay everything else is working as i expect how can i approach this particular thing as a new feature how can i take this expected behavior and say you know what i don't want to expect that we get an exception now i expect this works i expect that i should be able to wire tapping at multiple instances then i can come back and really focus on this test right i can say you know what like let me go um change all the code behind to try making this work i can change the expected assertions and that when i run this when i run the suite of tests all the other tests are still passing and then this new this test that i'd be altering i'm pointing with my hand sorry uh this test i would be altering which is the one we're looking at um that one would actually be um you know the one that's in flux that were that were we're changing up um and that when everything works that one starts to pass now and it's not throwing an exception so this is um that was a really long-winded way to say that this is something i want to come back and look at it's not doing what i want but at least now i have a test that tells me this is currently the expected behavior um why i think that's important um in general for uh for coded tests is that um it's it's kind of weird to think about i think sometimes people think about uh writing you know a coded test a unit test whatever it happens to be um they're like here's like a you know shiny feature it does you know thing x y and z so we should write tests that prove like you know it does things x y and z um this is almost the inverse where it's like that in my opinion like the feature doesn't do what i wanted to do it sucks like i'm not happy about it however it's not about whether it's doing the good thing or it's doing um you know it makes me happy like you have to remove that completely i'm actually testing that there is expected behavior right the expected behavior is not personally what i want but this is actually expected behavior there's a couple of really cool examples that i've had in my professional career where um we've come across bugs so we're you know we'd be refactoring something fixing another bug whatever it happens to be and we come across another bug and we go oh no um like what do we do like do we stop everything we're doing do we fix this at the same time uh maybe it's really trivial i don't know it could be any number of things but what's actually really cool is that if the code is already testable you can put a test in place that actually says we expect this bug right it sounds kind of silly but like you expect this bug to happen and what's really cool is um you know if you're using you know stories and tickets and whatever tracking systems maybe you file the bug and you have that great but when you have this test in place um what's really cool is that when someone fixes it they actually have now a broken test right you've by fixing the bug you've broken the test that actually checks for the bug but the reason that's cool is that you can usually flip a couple of conditions in the test and now they have a their test is already written for them so by writing the test you're actually kind of doing a bunch of the other work that will be coming anyway uh without having to go um you know pull apart code and stuff anyway um this is just an example of uh basically testing expected behavior even though it's not behavior we're happy about right okay so continuing on um now these examples change up a little bit we're going back to a default wiretap like the first one um it's still a single wiretap as we can see there's only one but what's changed now is that the base registration is multi-instance so the other ones used to say single instance here right these ones don't so what do we actually expect to happen well the default wiretap so no parameters specified here i was saying well if original registration was expecting new instances then when we wiretap by default maybe we should get new instances right so that's what this test actually checks so we'd resolve instance one right and then instance two these check that the wiretap actually worked but this line right here is special because it's saying instance one and instance two are not the same anymore they're not this test could probably be enhanced a little bit um it makes it a little bit trickier to read but this line in particular um i'd have to under i have to go double check the internals of what's actually happening with the wiretap but i believe that this method this body should run twice and what i might be able to do is actually say the first time it runs maybe store the base instance and the second time it runs compare the second base instance that's come in to the first one and they they should also be different instances um it's a small little enhancement that kind of proves that like this test was set up with um the right registration right it's not single instance anyway so that was another test um sort of the next ones that come up are extensions of that kind of uh you know very similar to the original ones where now i'm putting in explicitly we want single instance um this one is kind of inverse to the one that was throwing an exception so the base one is multi-instance right because i what i've highlighted does not have single instance anywhere um but we're saying explicitly that when we wire tap we now want to have a single instance coming back so the fact that this one works and i couldn't make it work the other way kind of bothers me so i do want to go back i keep saying it but i want to go back and fix that other uh functionality and make it so however this test will prove that um you know even though this is a multi-instance registration then when we go to wiretap we will get the same instances coming back when the wire tap occurs and that's because we've explicitly said here we want a single instance this next one again very similar but it's just that this one says new instances so this test should be essentially exactly like the default behavior right the default behavior here we'll go figure out that because our original registration up here does not have single instance that this parameter should be new instances so this tests the body of it looks exactly like the one that's two up that we've already looked at um these other ones um are pretty cool so in my my first video um i was talking about um a lot of stuff in auto fact maybe not a lot that's not the right way to say but there's um syntax and auto fact that if you were to do a registration and you said register type um register type a as interface x and then if you did register type b right after that as interface x when you go to resolve interface x you will get b back so it overrides registrations you will always take the latest registration if you're resolving a single service if you're doing an innumerable of them it will give you the setback however if you're doing a single one it will always give you the latest registration so when i was making the other video i was talking about well i think that if i do multiple wiretaps in a row i think maybe i should do overriding when i actually um and this is uh sort of the second really important thing i wanted to talk about in this video which is i'm kind of coming up with something right now that i don't know like i've said it a few times i don't know what the expected behavior should be um you know it's it's working but is it working as people would expect i don't know that's the goal so by writing these tests out i actually get to to really exercise the api i get to be the person that's like sure i'm building the implementation but i get to be the one going okay well i'm gonna i'm gonna set this code up you know i'm gonna try writing what a wiretap looks like and i get to be the one that says like you know what this syntax sucks like i don't care if it's it could be working perfectly but the syntax is garbage to work with it makes me not want to use it um so i again by writing these tests out i get to play with the api and go you know what this doesn't feel good or this this part feels great if i can make these other pieces kind of feel like that like um it really actually lets you use uh your code that you're writing so you get to think about both sides of it not just oh does it work but also does this feel good to use because it feels like crap to use you know you're going to have people writing crappy code to use your code that's hard to use and then everything gets crappy so it's a really good opportunity in my opinion that um you know when you're writing tests and stuff out and you have full control over the api that you're putting together if you don't then that's a different story but if you have full control over it it's a really good exercise to be like you know ask yourself seriously does this does this feel good to use like maybe i'll just give you an example maybe doing a wiretap and passing in this um anonymous function you're like you know what that feels really stupid like i don't like that a lot of the stuff in auto fact i mean they have some stuff with callbacks but they do this this builder pattern where they say register type but then they can say dot single instance right after so you keep chaining these methods up and that feels like that feels really good to use my wiretap method doesn't do that right so maybe i would want to say like dot wiretap and then where i'm passing in those explicit i'm scrolling up again sorry where i pass in these extra parameters maybe it would be really cool to say dot single instance right maybe that would be what people want i don't know um but by writing this out i've actually been able to play with it i do feel that having this callback function feels okay i felt having the extra parameter if you want to put it in for the explicit single instance or multi-instance that doesn't feel so bad um so i was i was reasonably happy with it but keep saying it it let me get to practice it right let me get to try out the code see if the api felt okay so one thing i learned touching on the previous video was i did say that maybe i do expect this behavior to override the wiretap when i tried it out and started using it more i was saying no like it it doesn't i actually personally believe that after using this if i were to put multiple wiretaps in place like this i do expect them to chain together so when you wire tap and then you wire tap after um the the first wire tap will then go into the second wire tab and then if you had a third that would go into the third and so on and so forth so i was like that's what i'm going to make i'm going to change the code so it's it fully supports us and it's consistent i'm going to come back to that word consistent at the end of all this because um that's sort of how i want to summarize this video but i wanted to make sure that i could get that functionality the cool thing is i do think that how i have the code behind if i want i can change up the api to actually allow overriding wire tabs so if you want if you knew or you were like i want to make sure nothing else is wiretapping i don't want to chain things together like i'm stopping this i think that i can change up the code maybe alter the api a little bit and actually say whatever wiretaps existed kill them off we're going to replace it with uh with a new one so i think i can write that if i go to do that i have all these tests in place still which is great i can make sure that i'm not breaking any functionality as i go to add new functionality so just to kind of summarize that part i did feel that having multiple wiretaps in a row should chain together so this test shows that the first wiretap the base instance we're expecting it to be the original test implementation right because it's a tightly registered up here then when we wire tap the second time you can see that the base instance is actually the wiretap instance from before and then we're going to return a new i was really creative with my naming it just says two on this type um so the second wiretap takes in the first wire tab and and then our assertions at the end here look very much like before because the default is single instance right single instance up here and we're implicitly saying we're going to rely because i didn't put the type or the parameter on here we're explicitly saying we want to take whatever the original one was these will also be single instance so you can see this set of checks down here says both instances are the second wiretap and both these instances are equal cool okay so one thing that was interesting about this uh is that it didn't work the first time i did it the test kept failing um and it kept failing because actually the way the code behind works is that it inverts the order of these wiretaps in a way that i didn't expect so the code behind actually made the assumption that your last wiretap right so the second one here it would actually make it go first and then your your first one would go second so it was completely inverted so it was really cool that once i had this test in place and i'm going like i don't get it like i wrote this test and this should this is the behavior i expect right why isn't that happening i could step through it and then i realized like i see my two wiretap registrations what's going on i haven't necessarily figured it out but uh it did invert the order so instead of adding to a list of wiretaps i insert them at the beginning and it fixes it again i have the test in place so if that if i accidentally later go oh why am i inserting things why don't i just add them to this list i have a test that will catch that for me um that's mostly it i have this last test here which is essentially just like um you know multiple wiretaps with the default um not single instance right so that we can do two wire taps on not a single instance and that these two wiretaps come back as different instances so that covered most of the scenarios that i could think of as i was going through in the past video um as i was kind of talking and and looking at the output with everyone it was like ah you know what that feels like a bug it's not what i expected so that's why this was important to me um i mentioned i wanted to talk about consistency uh sort of to wrap this up and i think that was a big takeaway for me is that when i'm trying to think about what's uh what behavior is expected and what's not i think part of it is kind of like um you know getting to use it you'll see like this you kind of have this gut feel about what feels right and wrong like if something wasn't obvious as you're using it and i had a different behavior you might go ah like maybe we should change that um however in my particular case right that situation where i throw an exception and it's not behavior i want my argument there is like at least it's consistent now so anytime someone has that that ordering of registrations with those parameters it will consistently throw that exception for people um i wanted to make sure that i was as i was going through this i thought about consistency because i think ultimately that's going to be one of the the biggest things that makes something feel good to use aside from the syntax i think if your api has behavior that is not consistent that's where people get like you have to really start writing you know like weird if statements around like you know people leaving comments i don't know why it does this but you know if this condition then like let's flip these parameters around now it does what i want so instead of people trying to code around your sort of crappy and consistent api um you've made it consistent from the start at least and i think that helps a lot so i think that's all i wanted to talk about um i just really wanted to demonstrate that while i'm not you know i people that have worked with me know that i love writing coded tests but i'm also not the person that's like oh tdd everything i think there's a time and place for it um but for me this was a cool example of i got to experiment with something put it together started you know it was keep saying it was working but how can i make it better so as i start to tweak it that's where i was like if i put tests in place i can have working snapshots that feel good and make sure they don't break as i keep evolving it so that's why i wanted to use

Frequently Asked Questions

What is wiretapping in Autofac?

Wiretapping in Autofac is a technique that allows you to inject your own implementation of a service while still consuming the originally registered service. It's essentially a way to intercept calls to a service, allowing you to modify or extend its behavior without altering the core application.

How do I test the behavior of my wiretap implementation?

I use unit tests with xUnit to validate the behavior of my wiretap implementation. By writing tests that cover various scenarios, I can ensure that the wiretap behaves as expected and that any changes I make don't break existing functionality.

What should I consider when designing the API for wiretapping?

When designing the API for wiretapping, I focus on consistency and usability. It's important to ensure that the API feels intuitive and behaves in a predictable manner. I often experiment with different designs and test them to see what feels good to use, aiming for a balance between functionality and ease of use.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload