IMPORTANT INFO: You're NOT supposed to write the attribute with the file path/line/column yourself. You're supposed to use a source generator to implement the interceptor for you. The source generator runs whenever anything changes un the source code, so it will update the location(s) if the interceptons. If you use source generators as you're supposed to, changing the code will not break it.
Dear @nickchapsas, if you think that interfaces are most abused feature in C# (or it is for the fact your biggest problem), I totally envy you. Please, hire me for such projects! I would love to have these kinds of problems. What I see on not-just-legacy codebases is that developers with 10+ years of experience in C# does not even know how DI works, or how to use interfaces generally. IMHO: A feature with the highest abusing factor in C# are exceptions - they are just used as `goto`s.
funny, I see the opposite pattern, people assuming the exceptions are just going to get handled somewhere :D and I always considered C# an escape from the java's exception fetish, where you'd pre-declare what exceptions a method might throw.
The one problem I see with this is that its going to be confusing to people who aren't expecting interceptions. For example, when mocking, you pass a stub into the constructor of the type you want to mock and thus its very clear why you get a certain result from a method call. In this case, your object is null yet you don't get expected behaviour when calling members on null. I guess you can step into the method call to figure out what's going on but it feels "wrong". Maybe they can add some tooling into the debugger to make this less weird? Also, what happens if two interceptors are registered for the same invocation? I assume the last registered interception is the one that executes. This would also lead to weird behaviour where I think tooling is necessary to make it obvious.
@@tcortega I don't dislike the idea, I just want to see it refined more, to remove the oddities from the feature before it gets shipped. I don't think its current state is appropriate as null invocations not leading to NRE's is black magic at best.
If two interceptors are registered on the same target, there's a compile-error, because what would constitute "last"? Ordered by class name? Wouldn't really make sense. This might change in a future iteration of the feature however
Mocks are overused, agreed. But I would much rather still use an interface and have a reusable fake in-memory implementation instead. This reduces the amount of setup code and performs better and is easier to understand. One potential danger is if the fake implementation and the real implementation differ, but mocks or interceptors can be just as bad in this regard.
I agree, because once I’ve developed near full test coverage I’m mocking most of the repository and the amount of mock code would be about the same as a cheap memory implementation of the repository.
@@AlgoristHQ I need my team to have it as simple as hitting run in visual studio and I also want to see test coverage in app code with NCrunch, so I’ve been migrating away from needing docker compose for integration tests.
I absolutely deeply hate this feature. It is way too magic, and I can see all the ways it will be abused to create unreadable and confusing code. Dependency injection and mocking has worked fine so far, I don't see how this solves anything.
You are not supposed to - literally in the proposal - use this manually. This is intended for use by libraries to have compile-time logic as opposed to runtime. Things like middleware is already "impossible" to understand the flow of, and this feature actually makes it easier to understand by allowing it to actually output understandable source code instead of reflection magic.
@@bondarenkodf to be fair, this is one of those features that you'll have to work with even if you hate it, as the primary reason for its existence is for use in Microsoft's own libraries like AspNetCore.
I really like the idea. One way to solve it would be that alongside each interceptor is a public static Func (public static Func null) for the first test and mocker.SetupGetBySlug(slug => new Movie() {...}) for the second. On dispose the mocker clears the Funs so that the interceptors call the original method.
That's how it would probably work, but you can't run parallel tests in one process easily Unless you detect it by thread/context and choose proper mock
While an interesting feature, and definitely one that'll be used in C# 12, the use of an interface is the proper approach here; there''s so much setup and magic happening, that the traditional and well known interface abstration/mocking approach comes across as simple and straight forward.
I'm just not seeing the value, at least not yet. Let's say the interceptors will be source generated and the usage will be simple. Still, I'd rather mock using Moq or Nsubstitute and setup the mocked methods within the test (if they need to differ between tests) than have a separate interceptor class with the mocking logic. It may just be too much magic for my taste. But I do agree that this could benefit people working on legacy codebases. One problem though, how do you use this new feature on a legacy project unless you migrate it to .NET 8?
The separate intercepting class is supposed to be source generated. You won't write a separate class manually. The experience (ideally) should be the same as the current mocking approach, except that there's no need for interfaces. Interceptor is a compiler feature, so it doesn't actually require .NET 8. It's just released with .NET 8. You can totally use a new compiler on an old code base on .NET Framework 4 for example.
this interceptors are fishy. I thik when they come up they will lead to very bad spaghetti like projects where a lot of thinks happen like magic. c# is static compiled language so you can refactore anything you want with easy and you do not have to worry that something breaks. adding interface should not be problem in legacy code.
Love it. There's a reason I don't want to use interfaces everywhere: it implies my code is ready for all sorts of implementations passed in, for everything I want to test. It's not. That's lots more work.
@@qj0n I think the point John is making is by making an Interface just so you can test something, you are signalling this is an Interface and others may start writing other implementations that you don't want them to. Using interfaces all over the place for mocking tests is not what Interfaces were originally created for.
@@robwalker4653 for me intending to replace a class and writing it in a way, which allows to replace it are two separate things. Even if I don't intend to replace a repository I still end up with repository possible to be replaced as this is a direct result of separating the conserns - repository should hide complexity of database and expose simple methods. I usually use interface here as I want to explicitly control the contract from the side of consumer
While I do with the premise (abuse of interfaces) I disagree with the interception to add "domain" functionality. Does somebody remember/used PostSharp? The main problem with interception is you are adding "sorcery" that is not obvious unless you explicitly point it out. In my case, I am moving towards only using interfaces just for IO and branching away from things like repositories.
I'm afraid I don't see it. The flexibility you have with a framework like moq would be very hard to achieve. You have to have the code for all possible uses of a method in the object you want to mock on a single interceptor, how are you going to know what are you mocking it for? If you could define one interceptor per test I could see it working, but that's not how that feature is going to work. Maybe with AsyncLocal you could pass some info or where this is coming from but, honestly, I don't even want to imagine the complexity of writing such a source generator, if it is at all possible.
The interceptor feature sounds a bit dangerous, but I'm open minded and interested in seeing how the feature develops. However, we can even now use tools such as passing delegates and using classes with virtual methods to create testable code :)
You can 'mock' statics. Just point your actual static property/method to an updateable static method and then set this method to what you need for testing. Done this for years for dates and principals. No need to pollute your class/constructor with bloat interfaces.
Can you point to an article or an example in any blog/video/stack overflow which shows this (Need not necessarily be this as main topic, but the sample shows an implementation )? I am very much interested to explore this option for my test cases.
@@chris-pee namespace System; using System.Diagnostics.CodeAnalysis; // you don't need to pollute with yet another interface in the ctor and tidies up the code. Can be with all non domain specific code [ExcludeFromCodeCoverage] public static class DateTimeOffset_ // Can be called help or anything { // Needs this order private static Func _CurrentDateTimeFunction = () => DateTimeOffset.Now; // Actual call public static DateTimeOffset Now => _CurrentDateTimeFunction(); // set it back to the actual DateTimeOffset.Now public static void UseDefault() => _CurrentDateTimeFunction = () => DateTimeOffset.Now; // Call in the arrangement to set the date ti its test value public static void SetDateTime(DateTimeOffset datetime) => _CurrentDateTimeFunction = () => datetime; }
@@kelbobk5 Honestly I don't see how it's better than using an interface/delegate, it's weird cause looking at this I would assume that I can change what is the current date time in my production code. With an interface and mock I can have clear distinction and only one method instead of 3 and property
One thing i dont understand, how ill you create an interceptor for a method that needs to return something different between tests? In your example you comment out the previous code. But now the previous test would break. Can you have multiple interceptors for the same line?
He mentions that issue. From what I am understanding, you would need to have some sourcegen code that detects the test change and compiles the change on the fly.
I wonder how many people remember Pex and Moles which allowed mocking of things which did not have interfaces. I also believe the product Typemock will also do the same but I've never tried it.
Also there is very interesting NDC video "Hacking C# from the inside". This video demonstrates how to do dynamically intercept running C# to change what is actually executed. An extremely interesting video and well worth a watch.
I used to work on a legacy solution (comprising 90 projects, mind you!) that had been created with no unit tests and no interfaces. There was an integration tests base class, but it was a little hell to deal with and introducing unit tests was a lost cause. Even my senior colleague with 15 years experience gave up on it. Being able to introduce unit tests for that project would be a life-changer for my ex-colleagues.
I have been asked a couple of times to add unit tests to legacy codebases. The issue we had was not only making sure that every inline database call and dependent service was covered, but that every change was also covered in the future. In the end, the cost was too high for the business. And that was with Fakes as an option.
I doubt this will change the way majority of people are writing tests and code. TypeMock and MS Fakes are there for very long time, allow for mocking pretty much anything without interfaces (by doing IL rewrites) but aren't that popular. Probably because of pricing, but even across big corporations which pay for VS Ultimate or have budget for TypeMock you still rarely see them used. And there are free, open source libraries with similar capabilities. So yeah, if you didn't want to write interfaces just for testing, you could do it even now and interceptors aren't going to change it.
I used to work with Ms Fakes years ago and it was horrible. Shims sometimes just didn't work (good luck debugging it), build time was very long and we even had issues with our build agents out of memory as this generator was very heavy
As long as we don't have to write the interceptors ourselves, hey why not. This would actually be useful in a legacy project I am currently working on. There's a lot of untestable stuff and transforming the codebase is just not feasable anymore at this point.
are you saying this stuff works only on source code, if the attribute needs the path to the file and the position, because that would be quite a limitation
When I heard of the new interceptors feature, this was my first thought as well - "will be great for testing". Previously, the only way to mock or stub out something that was either non virtual or static was to use Microsoft Fakes (using Shims) but that's only available to VS Enterprise users. (There are some other alternatives, but nothing free as far as I can tell). This will certainly change the way we do testing, win win.
The alternative is to just wrap your class in a castle dynamic proxy object, and inject an interceptor in there. Seems a lot easier as well, since you don't have to intercept on a invocation / per line level, but just on method level
@@ronsijm afaik you cannot inject an interceptor on a static method. and one nasty situation is extension methods. An extension method cannot be part of 'the interface' because then it wouldn't compile so inherently cannot be mocked but they modify state of the object they extend so they can be a place for untestable dependencies. Interceptors seem like @brooklyndev pointed out the use of fakes/shims but requires vs enterprise.
Still fail to see the point in mocking database interactions. 99/100 the reason your code will fail is the database. Yet we write unit tests that eliminate the database from testing. I get the example, but it should be pointed out, that if your unit tests don't test your database, then you're still not (really) testing.
You can do it with Shims in Fakes, but this is old technology, actually similar to this in principles (generates code on build, allowing to modify particular methods in runtime). It didn't work well ;)
@@kocot. ...unless you're doomed to relay on some poorly written library and you want to mock it. But then this feature from video won't help you either
@nick you have your MovieService which has a Create(Movie movie) method, but it just validates and does not create an actual movie object (it already is passed to him). Is that on purpose or just because it does not matter for the purpose of this video? I would expect that a create method creates instances (if validation succeeded) and gives the then created object to the repository.
Unfortunately I don't agree with using interceptors. Various other people in the comment section pointed out major drawbacks. I want to add that testing like this hampers find all references(the unit tests using the code) during minor refactoring, makes it difficult to do unit test code coverage reports. Also how is quality control software like SonarQube going to deal with this?
I can see the value of new interception-based frameworks to help test code that wasn't written to be testable. That being said, I wouldn't use such a tool to inform my future designs as this is not a feature that replicates well across other OO languages. Sure, DI introduces a lot of abstractions, but the same design can be consistently replicated in different languages to achieve the same end result. Furthermore, it allows code to express itself as having no stakes in a particular implementation of a dependency, which is something you might miss out on if you decide to leverage interception just to avoid creating abstractions. Is this code actually tailored for a specific DBMS or do we not care? The abstraction tends to make that question clear. Abstractions also reduce the scope of the refactoring effort when changes need to be applied and isolates the associated risks. I don't feel like providing less abstractions provides such a significant benefit to warrant going with interceptions as the default. That being said, I think that interception-based testing will be an interesting option in at least two cases that I can envision: 1) Intercepting calls to static methods or other non-mockable constructs provided by a framework. This is fairly straightforward: we use a framework to avoid reinventing the wheel, so why should testing code that leverages a framework also have to test the wheel? Shouldn't this be the handled by framework developers? 2) When performance is critical and the overhead of adding and managing abstractions incurs a cost that needs to be avoided. Sometimes, you might need for code to be as fast as possible. And this means reducing the number of virtual calls and reducing the number of collaborators. This often makes code less testable without a setup that correctly replicates production-level conditions. With interceptors, you can verify the interactions that your code introduces in such contexts.
As long as it doesn't become a crutch that enables bad design rather than going with TDD to help enable maintainable codebases. It seems like this could be abused (so can interfaces and mocks) if we're still thinking about testing _after_ writing the code like in the example in the video. I worry about long term maintenance and complexity. I definitely see applicability for third party classes that do not expose an interface/abstraction for easy testability; we should still use this in addition to still using interfaces where interfaces are actually more appropriate. In other words, still no silver bullet.
You suggested removing the interface from the one thing that actually makes sense to be behind an interface. And instead completely missed out on the fact that those IValidators should be the ones to rip out of the code. Validation should just be a pure function and shouldnt be hidden behind an interface?
Thinking of Interceptors... Would it be possible to use it to it to intercept ALL property setters? Like for a MVVM viewmodel to perform its OnPropertyChanged notifications without all the boilerplate.
Microsoft already kind of does this with their community toolkit package. It uses attributes and a source generator to get rid of that boiler plate you are referring too. It's a mixed bag in my opinion. I like the concept a lot but I wish they would've leaned more on convention instead of just attributes. Small things like "if the class has ViewModel at the end of the name...it is a view model so implement INotifyPropertyChanged and what not" or "if a private variable starts with p_ then it is a backing field for a property so generate the properties for it." I do like the idea of using an attribute for computed fields though. Something like [ComputedFrom(nameof(PropertyA), nameof(PropertyB)] comes to mind. One thing I do like is how they use [RelayCommand] as a way to source generate your ICommand properties. If it did a combination of convention and a couple well-placed attributes I think it would've been a lot better, but it is still much nicer than writing that boiler plate.
@@logank.70 I've never seen p_whatever as a convention for backing fields for properties.. But CommunityToolkit.Mvvm is open source, so you could take their source generator code and adapt it to your preferences if you want. For me, I'm happy with the attributes.
It looks like we kind of reinvented Shims from MS Fakes... Been there, don't go there ;) But to get to details - we need to change a built to make those tests working. You probably don't want to affect production builds, so there are two options: 1. Build two dlls, one for tests another for running(like MyLib.dll and MyLib.Testable.dll) plus some magic to switch them in tests 2. Add interceptors to Debug built, but not to release Then, we want one interception to handle multiple tests, so we intercept all calls and framework decides what mock code to run. At first glance it's doable if we run one test at time, otherwise hard to determine (especially if we intercept statics) The question is how deep dependencies should we intercept. If we test A, it uses B and B uses C and we want to intercept usage of C should we intercept calls inside B even though we are testing A? And what if B is in another project? And what if it's in nuget package? Are we expecting to see packages like *.Testable? (Just like we have Analyser packages for many nugets). I guess paackages should still use interfaces if it is useful for testing All in all, could be nice feature for legacy code bases, but when developing a testable solution, I'd not choose this as preferred way for testing
I agree with everything. However even the "legacy code bases" argument is very hard to make, since a legacy code base will probably be miles away from C# 12
I'm really not convinced by this interceptor. Also, I'm not a fan of moving code from one file to another to do almost the same thing. I prefer to centralize my test and the "Arrange" part (setup) in the same place than to separate everything.
if the method moves where the interceptor point's to, will the test fail? will the interceptor break? To me it feels like a fancy GOTO, but that is probably me.
I'm not sure how I feel about this to be honest. But isn't that always the case when a new way of doing something comes out 😅. It feel wrong that the mocked file (source generated file) needs to be in the main project, when it's seems to only be used by the test project. Also, one reason to mock out a dependency, is that you then don't end up having to create multipe versions of a class for different scenarios. This feels like it could become that, if you have tests that check for different scenarios. In the video you just editted the MockGetSlugAsync method. But that would break your other test. On the plus side, having worked with bigs projects that have legacy code, that has then had to be re-written/re-architectured, this would save a lot of time and allow us to get tests in quicker. Maybe after they're in, then you could move to interfaces and mocking out. I'll have to watch your other video on interceptors and look into this more. Watch this comment become severely dated in a few months/years 🤪
Nick you said the purpose of these is AOT. Now I'm normally all about doing things at compile time, but i just dont see why tests need to be AOT compiled, when you really just want to run tests quickly to get feedback. Why not stick to reflection or find a way to source generate the implementation to an interface rather than additionally needing to statically analyze every call a class will take from one of the test methods, just to get those line/column numbers...
It's not that interceptors are for AOT only. It's that interceptors (or something similar) is _required_ for some AOT scenarios since you can't do some things in AOT that you can do with JIT. Interceptors let's you do these things at compile time instead of at run time.
Yeah, that's the only case where I think this new feature should be used for mocking. Legacy code that is too costly to make testable and ideally isn't being changed often.
i think the best use case for this will be when wanting to have different types of builds. where u could write a separate interceptor for different clients and compile a version for each of these clients per interceptor class. Some questions i have are, do interceptors have conditional options? like if its Tuesday don't intercept or something random like that
Is this logic need make interceptors inside one project or can be just injected from above? I mean does this allow hack usual calls between 2 different dll's calls?
Interceptors only work in the project in which they're implemented. They change the compilation of that project. That's why Nick had to add them to the main project instead of the unit test project.
This sounds great, but I see a weakness: I usually need tests most when I am working on the code, especially with TDD. But in that case the line numbers might constantly change, so I have to adapt the interceptors all the time. Did I got it wrong? Is there a good solution?
@@nickchapsas I would go one further and say if you're writing any line numbers/characters manually then you're doing it wrong (and setting yourself and your team up for a world of pain!)
well, I am not sure we should be chaning anything in the main project. and this is not much different to mocking as the mock libraries does this behind the scenes using similar concepts to interception like proxies, etc. But I agree with not requiring interfaces all over the place. this is what TDD/component tests encourage, the contract we really care about is the one the system has with the external players, like API callers, Queues, DBs, downstream services, files, logs, etc. internally it does not matter if we have interfaces are not, thinking this ways will make the so called "unit tests" obsolete, the high-level tests with the TestWebApplicationFactory is all we need, then we can have an in-memory represntation of those external players using mocks or fake implementations, it does not matter as long as we are only intercepting in the tests and only the external points of the system
I am a strong advocate of testing the binary that will be deployed, not code that is 'different' through weaving. The most pragmatic way forward is having integration tests of the public API/interfaces. Internal classes, DTOs etc. count towards the code coverage of the integration tests. Unit testing every single class, every single class being mockable/having an interface is a pain with refactoring.
Same here. I totally agree! There is several reasons why the binary will not be able to be weaved in the first place. For example I have part of the total code base being its own project with tests and results in a private nuget package. Other parts of our code base will use the nuget package. This type of waiving would simply not work. I also agree with you and Nick that not everything needs to be an interface and they are overused. Maybe one of the reasons is that the 'unit' of unit testing is not necessarily 'clear'? Maybe because examples and blog posts always focus on a small class. If one would write a custom version of some list, stack or other collection, these ideally should be unit tested, where the unit is the thing itself. Then for the rest there is exactly what you mentioned; unit tests on larger scale behaviour all the way up to basically calling the Api method. - Martin Fowler calls it solitary vs. sociable. - Dennis Doomen's 17 laws of TDD; * from 2: " accept that you need tests on different levels (e.g. class, component, module, API, UI)" * from 3: "Choose the right scope for your tests." and "a test-per-class is almost always the wrong approach" * from 16: "Avoid overuse of mocking libraries." Even if I imagine a package that would do this, I struggle to see how a single intercept would do multiple different things, like succeed and return different things for a couple of tests but also expectedly fail for another. And I think that parallel tests might be an issue too.
I feel in this scenario it's a really roundabout and magical way of replacing a function at runtime. This is done in other languages, and so perhaps the warts of doing that translate well over to this novel method.
I dont remember did you explain it in previous video, but... What if I've chaged the file a little bit... should I fix all interceptor data (line and position) for each of hundreds tests where it uses?)
Although I agree with many of the critiques here, I could see this being useful when mocking third-party code you don't control. I ran into a situation once where I was using a concrete class from the Twilio SDK that didn't have an interface, so I couldn't mock it. If interceptors existed, I could've mocked it just fine. On that note, I'd be curious as to how deep this can go. Could I intercept some internal code in a third-party library, forcing it to do exactly what I want?
Just use interfaces to mock IO abstractions. Use classes if you need to keep track of changes during the lifetime of the object and use statics methods in any other case.
If a branch depends on the return of a static, then your test setup can get very obscure only to be able to cover that specific branch. Combine static with statics, have nested branches, and see your tests exploding. You need to decouple. If you have mostly flat code then using statics is fine, but soon enough branching will crop up.
@@Fred-yq3fs if your code has statics and nested branches and more statics then you need to refactor it. Have you ever wondered how the Linux kernel works without injecting a single interface? If the most used system ever built can live without them in sure your API with a call to the dB and return an Ok can live without 236556483338847474 interfaces
This could be very useful to create unit tests of legacy code that doesn't use DI. I wonder placing generated interceptors in generated namespaces and then using for that namespace in the text class file that needs it. Would that allow the separation of interceptors to respond with this different results, as needed by tests? hmm.. not sure.
last video about interceptor I thought to my self, this will be useless for me, now I can see the use in testing, I'm just not sure how useful it will be compared to standard mocking because it is done through extensions which are static, not sure if it will be possible to run tests with different mocks
Because you want to generate the code only during test and not for the actual debug/release and get rid of it afterward, that's why Nick told us to assume the code is generated.
@@benomine The problem is that interceptor methods have to be accessible by the intercepted code. So basically, the generated code has to be in your "non-test" dll/exe, whether it's called by your test code or not.
And of course it supposed to be gitignored because of absolute path... So it seems this have to be generated just before test run and deleted after the test done.
As I understand, only one interceptor may point to the same file,line,char… having two would give you a compile error… Another problem I see here is that the interceptor will always be overruling the method and thus it will affect the actual binary… so whenever the method is called it will return null!
I expect rider, visual studio, or Roslyn will probably get a validator for if these are mapping correctly, especially if an agreed upon nomenclature arises
I'm not thrilled about Interceptors for test mocking purposes. It feels like too clunky of a system, at least until some serious work is put into doing a code-generating mock framework to create them. BUT then I have to ask what the benefit would be to using interceptors over existing proxy mechanisms? Or even Fody-style code weaving? It's hard for me to see how Interceptors either enable functionality we don't already have OR make improvements over existing functionality (like perf improvements, etc).
Interesting, I was using test harnesses in the 80s that could auto-write interceptors, well procedural 'C'. I can see this would be useful for legacy, saves creating seams. To find out how automate this (just not doing this by hand) presumably watch the other video?
Wow, that is really great feature for testing! But I want to ask you to use relative paths instead of absolute paths in your videos. It may confusing someone, especially newcomers, who are learning C# And they may wrongly thinking that it is ok to have absolute paths in code, which is obviously not ok.
This is very similar to how mocking functions works in python - and is really hard to maintain or understand the test. This normally shows a problem with the design more than an issue with the testing framework from my experience.
This is going to need a much better way to target the function call than specifying the file path (not guaranteed to be the same across developer machines) and line numbers and character offsets (likely to change when the target file is modified). This is far too unpolished a solution for me to get excited about or recommend to my teammates.
I totally agree. I like the idea of interceptors, but can see how this will break because of the reasons you´ve just pointed out. Legacy code can be a use case, though.
When line numbers and character offsets change, the attribute on the intercepting code will also change, because it's all generated on the fly, NOT written by a human. All that ugly details should be invisible and "just work" for 99.9% of C# devs.
In corporate world it would be beneficial to have possibility to mock static classes and methods. This is because of "quality gates" which require certain level of code coverage. Because of this people are avoiding static classes. But these interceptors will not help. They require to give exact location in code which should be intercepted. The problem is that different test environments require different path.
This will be tricky since the interceptor is declared once per assembly. Means the interception logic has to be shared amongst all the unit tests in the same project.
Yes: If used for mocking then 1 method must be able to be intercepted by n interceptors. That leaves the problem of knowing which one to use, meaning an interceptor should exist in some sort of context that would make it unique. If interceptors stay as they are then they could be used as a life-saver, in legacy code where refactor to inject is not a viable option, but that would not be pretty.
yep, maybe if we do not need virtual extends or contract management, interceptor maybe useful for similar cases such as testing. Otherwise, even mappers kill readability, interceptor will have a nuke effect.
In general the idea is very cool, but I suppose it will not be implemented at the nearest future as we'll need to sacrifice the "on-the-fly re-compilation" feature during tests debugging
The interceptor look as an interesting feature in coder's arsenal to resolve the mocking problem. However I noticed one problem. Firstly there was an absolute path mentioned where the extension will be applied and secondly there were mentioned the line of code and column where to do it. The frist time we add a space or a new line if I understood correctly that should not work. On the other side that full absolute path won't be the same for each member of some development team. So we have 2 problems but other than that is a nice feature.
I don't know that it's right to say "interfaces are _abused,"_ though they may certainly be _overused._ Not everything _needs_ to be an interface, but I don't know of anything that's _harmed_ be being one.
I love interceptors. I understand many people's worries, but I think it's generally overblown. IDEs will, similar to implicit conversions, give you intellisense whenever they're intercepted.
This is the wrong way in my opinion, we should have like special operator called, for example, "interfaceof", so we can extract an interface of a class if it covers all non-static public properties/method. It's cleaner, simpler, less verbose and the most important: intuitive. That would not only simplify mocking of our own services, but most importantly we will be able to abstract from third party services which authors didn't thought about exposing interfaces (today we need some libraries that do assembly magic at runtime).
I would be happe to have this feature. It's hard to count how many things I've had to fix internally using Xamarin.Forms. So, instead of having the patched XF nuget package everything I need is to fix the wrong method. It would be nice to have access to private/protected/internal things too. It would be extreme cool to be able to combine this feature with the Source Link too. Just imagine you have some exception in the third-party library, you debug it using source link, then patch the method in 1-2-3 clicks!
Personally I struggle to see how maintainable code will be that uses this feature. Surely if you add in a new line or change the length of a variable or function name you’ll have to work everything out all over again?
Am I right in thinking that the test will fail when someone visit the class and decide to add a comment line as the interceptor won't be executed properly ?
Just give us more evolved constraints, such as the C++ 'Concepts', exposing possibilities by removing possibilities, being able to funnel down into functionalities that become inferable by the compiler.
You can of course mock classes, assuming the methods you want to mock are marked virtual. Perhaps better than making a god damn interface for every god damn class. :)
This is so wrong i don't even know where to start. Why is the static class in the main project and not the test project? Do I need to build my code differently to run tests? What if I want to return something more interesting than `null` or `true`? Do I need to hardcode the same object in two places (test code and the interceptor generator)? What if different tests need different values from the mocked classes (e.g. to test how the MovieService reacts to a validation error)? Just use interfaces. They're simple, obvious, and your IDE can auto-generate one.
IMPORTANT INFO:
You're NOT supposed to write the attribute with the file path/line/column yourself. You're supposed to use a source generator to implement the interceptor for you. The source generator runs whenever anything changes un the source code, so it will update the location(s) if the interceptons. If you use source generators as you're supposed to, changing the code will not break it.
Awesome, I was just writing an attribute to do this work for you at run time. Maybe both approaches can be useful
Oh please God, don’t tell me that Nick is jumping on the interceptor train
Dear @nickchapsas, if you think that interfaces are most abused feature in C# (or it is for the fact your biggest problem), I totally envy you. Please, hire me for such projects! I would love to have these kinds of problems.
What I see on not-just-legacy codebases is that developers with 10+ years of experience in C# does not even know how DI works, or how to use interfaces generally.
IMHO: A feature with the highest abusing factor in C# are exceptions - they are just used as `goto`s.
funny, I see the opposite pattern, people assuming the exceptions are just going to get handled somewhere :D and I always considered C# an escape from the java's exception fetish, where you'd pre-declare what exceptions a method might throw.
The one problem I see with this is that its going to be confusing to people who aren't expecting interceptions. For example, when mocking, you pass a stub into the constructor of the type you want to mock and thus its very clear why you get a certain result from a method call.
In this case, your object is null yet you don't get expected behaviour when calling members on null. I guess you can step into the method call to figure out what's going on but it feels "wrong".
Maybe they can add some tooling into the debugger to make this less weird?
Also, what happens if two interceptors are registered for the same invocation? I assume the last registered interception is the one that executes. This would also lead to weird behaviour where I think tooling is necessary to make it obvious.
@@tcortega I don't dislike the idea, I just want to see it refined more, to remove the oddities from the feature before it gets shipped. I don't think its current state is appropriate as null invocations not leading to NRE's is black magic at best.
If two interceptors are registered on the same target, there's a compile-error, because what would constitute "last"? Ordered by class name? Wouldn't really make sense.
This might change in a future iteration of the feature however
Exactly, there's a lot of implicit trickery going on. At least with mocks we know the thing is mocked.
Mocks are overused, agreed. But I would much rather still use an interface and have a reusable fake in-memory implementation instead. This reduces the amount of setup code and performs better and is easier to understand. One potential danger is if the fake implementation and the real implementation differ, but mocks or interceptors can be just as bad in this regard.
I agree, because once I’ve developed near full test coverage I’m mocking most of the repository and the amount of mock code would be about the same as a cheap memory implementation of the repository.
This is where containerization comes into play. Don’t mock repos! Just used a containerized database!
@@AlgoristHQ I use Testcontainers for that so I can keep it playable in visual studio without extra setup.
@@xeekk I use the built in docker support in vs2022 and docker-compose.
@@AlgoristHQ I need my team to have it as simple as hitting run in visual studio and I also want to see test coverage in app code with NCrunch, so I’ve been migrating away from needing docker compose for integration tests.
I absolutely deeply hate this feature. It is way too magic, and I can see all the ways it will be abused to create unreadable and confusing code. Dependency injection and mocking has worked fine so far, I don't see how this solves anything.
You are not supposed to - literally in the proposal - use this manually.
This is intended for use by libraries to have compile-time logic as opposed to runtime.
Things like middleware is already "impossible" to understand the flow of, and this feature actually makes it easier to understand by allowing it to actually output understandable source code instead of reflection magic.
😢the feature is good, I'm developing a new design principal
so, don't use it then.
@@bondarenkodf to be fair, this is one of those features that you'll have to work with even if you hate it, as the primary reason for its existence is for use in Microsoft's own libraries like AspNetCore.
@@bondarenkodfUnfortunately some of us write code with other people
Aren't you advertising the wrong tool for an already solved problem?
I really like the idea. One way to solve it would be that alongside each interceptor is a public static Func (public static Func null) for the first test and mocker.SetupGetBySlug(slug => new Movie() {...}) for the second. On dispose the mocker clears the Funs so that the interceptors call the original method.
That's how it would probably work, but you can't run parallel tests in one process easily
Unless you detect it by thread/context and choose proper mock
While an interesting feature, and definitely one that'll be used in C# 12, the use of an interface is the proper approach here; there''s so much setup and magic happening, that the traditional and well known interface abstration/mocking approach comes across as simple and straight forward.
6:41 THIS is a pretty good easter egg =)
the Doug flash when you said "THIS" made me laugh like crazy!
I'm so glad someone appreciates it. I thought of it during editing and I couldn't stop laughing
I'm just not seeing the value, at least not yet. Let's say the interceptors will be source generated and the usage will be simple. Still, I'd rather mock using Moq or Nsubstitute and setup the mocked methods within the test (if they need to differ between tests) than have a separate interceptor class with the mocking logic. It may just be too much magic for my taste. But I do agree that this could benefit people working on legacy codebases. One problem though, how do you use this new feature on a legacy project unless you migrate it to .NET 8?
The separate intercepting class is supposed to be source generated. You won't write a separate class manually. The experience (ideally) should be the same as the current mocking approach, except that there's no need for interfaces.
Interceptor is a compiler feature, so it doesn't actually require .NET 8. It's just released with .NET 8. You can totally use a new compiler on an old code base on .NET Framework 4 for example.
@@ChenfengBao alright, that does make it sound much better.
ms fakes do the legacy part already without updating to .net 8, end of strory
this interceptors are fishy. I thik when they come up they will lead to very bad spaghetti like projects where a lot of thinks happen like magic. c# is static compiled language so you can refactore anything you want with easy and you do not have to worry that something breaks. adding interface should not be problem in legacy code.
This will be a real life saver for my legacy C# 12 apps 😬
This pretty much replaces one headache with another headache. :)
Love it. There's a reason I don't want to use interfaces everywhere: it implies my code is ready for all sorts of implementations passed in, for everything I want to test. It's not. That's lots more work.
Well, if it's not then you probably will have troubles with mocking it anyway
@@qj0n I think the point John is making is by making an Interface just so you can test something, you are signalling this is an Interface and others may start writing other implementations that you don't want them to. Using interfaces all over the place for mocking tests is not what Interfaces were originally created for.
@@robwalker4653 for me intending to replace a class and writing it in a way, which allows to replace it are two separate things. Even if I don't intend to replace a repository I still end up with repository possible to be replaced as this is a direct result of separating the conserns - repository should hide complexity of database and expose simple methods. I usually use interface here as I want to explicitly control the contract from the side of consumer
While I do with the premise (abuse of interfaces) I disagree with the interception to add "domain" functionality. Does somebody remember/used PostSharp? The main problem with interception is you are adding "sorcery" that is not obvious unless you explicitly point it out. In my case, I am moving towards only using interfaces just for IO and branching away from things like repositories.
I'm afraid I don't see it. The flexibility you have with a framework like moq would be very hard to achieve. You have to have the code for all possible uses of a method in the object you want to mock on a single interceptor, how are you going to know what are you mocking it for? If you could define one interceptor per test I could see it working, but that's not how that feature is going to work. Maybe with AsyncLocal you could pass some info or where this is coming from but, honestly, I don't even want to imagine the complexity of writing such a source generator, if it is at all possible.
I am really excited!!
The interceptor feature sounds a bit dangerous, but I'm open minded and interested in seeing how the feature develops. However, we can even now use tools such as passing delegates and using classes with virtual methods to create testable code :)
Yes I am also using virtual methods to run unit tests and don't use interfaces
I think interceptors may be very useful in code self-analysis and something in the lines of memory inspection and performance-counters alike
interesting feature, going to check interceptors out soon, thanks!
You can 'mock' statics. Just point your actual static property/method to an updateable static method and then set this method to what you need for testing. Done this for years for dates and principals. No need to pollute your class/constructor with bloat interfaces.
Can you point to an article or an example in any blog/video/stack overflow which shows this (Need not necessarily be this as main topic, but the sample shows an implementation )? I am very much interested to explore this option for my test cases.
What's "an updateable static method"?
@@chris-pee namespace System;
using System.Diagnostics.CodeAnalysis;
// you don't need to pollute with yet another interface in the ctor and tidies up the code. Can be with all non domain specific code
[ExcludeFromCodeCoverage]
public static class DateTimeOffset_ // Can be called help or anything
{
// Needs this order
private static Func _CurrentDateTimeFunction = () => DateTimeOffset.Now;
// Actual call
public static DateTimeOffset Now => _CurrentDateTimeFunction();
// set it back to the actual DateTimeOffset.Now
public static void UseDefault() => _CurrentDateTimeFunction = () => DateTimeOffset.Now;
// Call in the arrangement to set the date ti its test value
public static void SetDateTime(DateTimeOffset datetime) => _CurrentDateTimeFunction = () => datetime;
}
@@kelbobk5 Honestly I don't see how it's better than using an interface/delegate, it's weird cause looking at this I would assume that I can change what is the current date time in my production code. With an interface and mock I can have clear distinction and only one method instead of 3 and property
One thing i dont understand, how ill you create an interceptor for a method that needs to return something different between tests? In your example you comment out the previous code. But now the previous test would break. Can you have multiple interceptors for the same line?
He mentions that issue. From what I am understanding, you would need to have some sourcegen code that detects the test change and compiles the change on the fly.
I wonder how many people remember Pex and Moles which allowed mocking of things which did not have interfaces. I also believe the product Typemock will also do the same but I've never tried it.
Also there is very interesting NDC video "Hacking C# from the inside". This video demonstrates how to do dynamically intercept running C# to change what is actually executed. An extremely interesting video and well worth a watch.
I have never heard of any of this, I really need to look into it
I used to work on a legacy solution (comprising 90 projects, mind you!) that had been created with no unit tests and no interfaces. There was an integration tests base class, but it was a little hell to deal with and introducing unit tests was a lost cause. Even my senior colleague with 15 years experience gave up on it. Being able to introduce unit tests for that project would be a life-changer for my ex-colleagues.
I have been asked a couple of times to add unit tests to legacy codebases. The issue we had was not only making sure that every inline database call and dependent service was covered, but that every change was also covered in the future. In the end, the cost was too high for the business. And that was with Fakes as an option.
Legacy code bases have a problem though. You'd have to upgrade them to .Net 8 first. 🤔
I doubt this will change the way majority of people are writing tests and code.
TypeMock and MS Fakes are there for very long time, allow for mocking pretty much anything without interfaces (by doing IL rewrites) but aren't that popular.
Probably because of pricing, but even across big corporations which pay for VS Ultimate or have budget for TypeMock you still rarely see them used.
And there are free, open source libraries with similar capabilities.
So yeah, if you didn't want to write interfaces just for testing, you could do it even now and interceptors aren't going to change it.
I used to work with Ms Fakes years ago and it was horrible. Shims sometimes just didn't work (good luck debugging it), build time was very long and we even had issues with our build agents out of memory as this generator was very heavy
As long as we don't have to write the interceptors ourselves, hey why not. This would actually be useful in a legacy project I am currently working on. There's a lot of untestable stuff and transforming the codebase is just not feasable anymore at this point.
I made it as far as providing the line number and character location before deciding this is worse than mocking.
are you saying this stuff works only on source code, if the attribute needs the path to the file and the position, because that would be quite a limitation
Mock frameworks still have a lot more functionality, just for example, verify.
When I heard of the new interceptors feature, this was my first thought as well - "will be great for testing". Previously, the only way to mock or stub out something that was either non virtual or static was to use Microsoft Fakes (using Shims) but that's only available to VS Enterprise users. (There are some other alternatives, but nothing free as far as I can tell). This will certainly change the way we do testing, win win.
The alternative is to just wrap your class in a castle dynamic proxy object, and inject an interceptor in there. Seems a lot easier as well, since you don't have to intercept on a invocation / per line level, but just on method level
@@ronsijm afaik you cannot inject an interceptor on a static method. and one nasty situation is extension methods. An extension method cannot be part of 'the interface' because then it wouldn't compile so inherently cannot be mocked but they modify state of the object they extend so they can be a place for untestable dependencies. Interceptors seem like @brooklyndev pointed out the use of fakes/shims but requires vs enterprise.
Still fail to see the point in mocking database interactions. 99/100 the reason your code will fail is the database. Yet we write unit tests that eliminate the database from testing. I get the example, but it should be pointed out, that if your unit tests don't test your database, then you're still not (really) testing.
cant you mock concrete classes using moq (or other mocking libraries) already?
You can do it with Shims in Fakes, but this is old technology, actually similar to this in principles (generates code on build, allowing to modify particular methods in runtime). It didn't work well ;)
you can and it's even a recommended approach for Azure SDK, the method simply need's to be virtual, not a big deal
@@kocot. ...unless you're doomed to relay on some poorly written library and you want to mock it. But then this feature from video won't help you either
@nick you have your MovieService which has a Create(Movie movie) method, but it just validates and does not create an actual movie object (it already is passed to him). Is that on purpose or just because it does not matter for the purpose of this video? I would expect that a create method creates instances (if validation succeeded) and gives the then created object to the repository.
Unfortunately I don't agree with using interceptors. Various other people in the comment section pointed out major drawbacks.
I want to add that testing like this hampers find all references(the unit tests using the code) during minor refactoring, makes it difficult to do unit test code coverage reports. Also how is quality control software like SonarQube going to deal with this?
I can see the value of new interception-based frameworks to help test code that wasn't written to be testable. That being said, I wouldn't use such a tool to inform my future designs as this is not a feature that replicates well across other OO languages. Sure, DI introduces a lot of abstractions, but the same design can be consistently replicated in different languages to achieve the same end result.
Furthermore, it allows code to express itself as having no stakes in a particular implementation of a dependency, which is something you might miss out on if you decide to leverage interception just to avoid creating abstractions. Is this code actually tailored for a specific DBMS or do we not care? The abstraction tends to make that question clear.
Abstractions also reduce the scope of the refactoring effort when changes need to be applied and isolates the associated risks. I don't feel like providing less abstractions provides such a significant benefit to warrant going with interceptions as the default.
That being said, I think that interception-based testing will be an interesting option in at least two cases that I can envision:
1) Intercepting calls to static methods or other non-mockable constructs provided by a framework. This is fairly straightforward: we use a framework to avoid reinventing the wheel, so why should testing code that leverages a framework also have to test the wheel? Shouldn't this be the handled by framework developers?
2) When performance is critical and the overhead of adding and managing abstractions incurs a cost that needs to be avoided. Sometimes, you might need for code to be as fast as possible. And this means reducing the number of virtual calls and reducing the number of collaborators. This often makes code less testable without a setup that correctly replicates production-level conditions. With interceptors, you can verify the interactions that your code introduces in such contexts.
As long as it doesn't become a crutch that enables bad design rather than going with TDD to help enable maintainable codebases. It seems like this could be abused (so can interfaces and mocks) if we're still thinking about testing _after_ writing the code like in the example in the video. I worry about long term maintenance and complexity. I definitely see applicability for third party classes that do not expose an interface/abstraction for easy testability; we should still use this in addition to still using interfaces where interfaces are actually more appropriate. In other words, still no silver bullet.
You suggested removing the interface from the one thing that actually makes sense to be behind an interface. And instead completely missed out on the fact that those IValidators should be the ones to rip out of the code. Validation should just be a pure function and shouldnt be hidden behind an interface?
Thinking of Interceptors... Would it be possible to use it to it to intercept ALL property setters?
Like for a MVVM viewmodel to perform its OnPropertyChanged notifications without all the boilerplate.
Microsoft already kind of does this with their community toolkit package. It uses attributes and a source generator to get rid of that boiler plate you are referring too. It's a mixed bag in my opinion. I like the concept a lot but I wish they would've leaned more on convention instead of just attributes. Small things like "if the class has ViewModel at the end of the name...it is a view model so implement INotifyPropertyChanged and what not" or "if a private variable starts with p_ then it is a backing field for a property so generate the properties for it." I do like the idea of using an attribute for computed fields though. Something like [ComputedFrom(nameof(PropertyA), nameof(PropertyB)] comes to mind. One thing I do like is how they use [RelayCommand] as a way to source generate your ICommand properties. If it did a combination of convention and a couple well-placed attributes I think it would've been a lot better, but it is still much nicer than writing that boiler plate.
@@logank.70 Interesting. I was aware of the RelayCommandAttribute, but not the weird logic for 'p_' names.
@billy65bob they didn't go that route unfortunately. I just wish they had done things like that.
@@logank.70 I've never seen p_whatever as a convention for backing fields for properties.. But CommunityToolkit.Mvvm is open source, so you could take their source generator code and adapt it to your preferences if you want.
For me, I'm happy with the attributes.
It looks like we kind of reinvented Shims from MS Fakes... Been there, don't go there ;)
But to get to details - we need to change a built to make those tests working. You probably don't want to affect production builds, so there are two options:
1. Build two dlls, one for tests another for running(like MyLib.dll and MyLib.Testable.dll) plus some magic to switch them in tests
2. Add interceptors to Debug built, but not to release
Then, we want one interception to handle multiple tests, so we intercept all calls and framework decides what mock code to run. At first glance it's doable if we run one test at time, otherwise hard to determine (especially if we intercept statics)
The question is how deep dependencies should we intercept. If we test A, it uses B and B uses C and we want to intercept usage of C should we intercept calls inside B even though we are testing A? And what if B is in another project?
And what if it's in nuget package? Are we expecting to see packages like *.Testable? (Just like we have Analyser packages for many nugets). I guess paackages should still use interfaces if it is useful for testing
All in all, could be nice feature for legacy code bases, but when developing a testable solution, I'd not choose this as preferred way for testing
I agree with everything. However even the "legacy code bases" argument is very hard to make, since a legacy code base will probably be miles away from C# 12
I would not go this route, but I do wish C# had a good level of AOP support like Java.
I'm really not convinced by this interceptor. Also, I'm not a fan of moving code from one file to another to do almost the same thing. I prefer to centralize my test and the "Arrange" part (setup) in the same place than to separate everything.
if the method moves where the interceptor point's to, will the test fail? will the interceptor break?
To me it feels like a fancy GOTO, but that is probably me.
I'm not sure how I feel about this to be honest. But isn't that always the case when a new way of doing something comes out 😅. It feel wrong that the mocked file (source generated file) needs to be in the main project, when it's seems to only be used by the test project. Also, one reason to mock out a dependency, is that you then don't end up having to create multipe versions of a class for different scenarios. This feels like it could become that, if you have tests that check for different scenarios. In the video you just editted the MockGetSlugAsync method. But that would break your other test.
On the plus side, having worked with bigs projects that have legacy code, that has then had to be re-written/re-architectured, this would save a lot of time and allow us to get tests in quicker. Maybe after they're in, then you could move to interfaces and mocking out. I'll have to watch your other video on interceptors and look into this more. Watch this comment become severely dated in a few months/years 🤪
Nick you said the purpose of these is AOT. Now I'm normally all about doing things at compile time, but i just dont see why tests need to be AOT compiled, when you really just want to run tests quickly to get feedback. Why not stick to reflection or find a way to source generate the implementation to an interface rather than additionally needing to statically analyze every call a class will take from one of the test methods, just to get those line/column numbers...
It's not that interceptors are for AOT only. It's that interceptors (or something similar) is _required_ for some AOT scenarios since you can't do some things in AOT that you can do with JIT.
Interceptors let's you do these things at compile time instead of at run time.
What kind of Result class do you use?
This could be really useful when working with some old code which because of its complexity cannot be easily refactored
Yeah, that's the only case where I think this new feature should be used for mocking.
Legacy code that is too costly to make testable and ideally isn't being changed often.
i think the best use case for this will be when wanting to have different types of builds. where u could write a separate interceptor for different clients and compile a version for each of these clients per interceptor class.
Some questions i have are, do interceptors have conditional options? like if its Tuesday don't intercept or something random like that
Is this logic need make interceptors inside one project or can be just injected from above?
I mean does this allow hack usual calls between 2 different dll's calls?
Interceptors only work in the project in which they're implemented. They change the compilation of that project. That's why Nick had to add them to the main project instead of the unit test project.
This sounds great, but I see a weakness: I usually need tests most when I am working on the code, especially with TDD. But in that case the line numbers might constantly change, so I have to adapt the interceptors all the time. Did I got it wrong? Is there a good solution?
The idea is that all this will be code/source generated so file location or line/characters don't matter because code will be writing that constantly
@@nickchapsas I would go one further and say if you're writing any line numbers/characters manually then you're doing it wrong (and setting yourself and your team up for a world of pain!)
@@nickchapsas So the code generator will keep track of the code locations I want to "mock"?
@@michaelgehling6742 Correct
well, I am not sure we should be chaning anything in the main project. and this is not much different to mocking as the mock libraries does this behind the scenes using similar concepts to interception like proxies, etc. But I agree with not requiring interfaces all over the place. this is what TDD/component tests encourage, the contract we really care about is the one the system has with the external players, like API callers, Queues, DBs, downstream services, files, logs, etc. internally it does not matter if we have interfaces are not, thinking this ways will make the so called "unit tests" obsolete, the high-level tests with the TestWebApplicationFactory is all we need, then we can have an in-memory represntation of those external players using mocks or fake implementations, it does not matter as long as we are only intercepting in the tests and only the external points of the system
I am a strong advocate of testing the binary that will be deployed, not code that is 'different' through weaving.
The most pragmatic way forward is having integration tests of the public API/interfaces. Internal classes, DTOs etc. count towards the code coverage of the integration tests. Unit testing every single class, every single class being mockable/having an interface is a pain with refactoring.
Same here. I totally agree! There is several reasons why the binary will not be able to be weaved in the first place. For example I have part of the total code base being its own project with tests and results in a private nuget package. Other parts of our code base will use the nuget package. This type of waiving would simply not work.
I also agree with you and Nick that not everything needs to be an interface and they are overused. Maybe one of the reasons is that the 'unit' of unit testing is not necessarily 'clear'? Maybe because examples and blog posts always focus on a small class. If one would write a custom version of some list, stack or other collection, these ideally should be unit tested, where the unit is the thing itself. Then for the rest there is exactly what you mentioned; unit tests on larger scale behaviour all the way up to basically calling the Api method.
- Martin Fowler calls it solitary vs. sociable.
- Dennis Doomen's 17 laws of TDD;
* from 2: " accept that you need tests on different levels (e.g. class, component, module, API, UI)"
* from 3: "Choose the right scope for your tests." and "a test-per-class is almost always the wrong approach"
* from 16: "Avoid overuse of mocking libraries."
Even if I imagine a package that would do this, I struggle to see how a single intercept would do multiple different things, like succeed and return different things for a couple of tests but also expectedly fail for another. And I think that parallel tests might be an issue too.
> Just don't use
I feel in this scenario it's a really roundabout and magical way of replacing a function at runtime. This is done in other languages, and so perhaps the warts of doing that translate well over to this novel method.
Probably the first time that I not agree your approach. It’s weird. The tracking of mock is a phantom in the code. :/
I dont remember did you explain it in previous video, but...
What if I've chaged the file a little bit... should I fix all interceptor data (line and position) for each of hundreds tests where it uses?)
It's all auto generated. You don't have to write the location, line or character by hand
@@nickchapsasin this case it makes sense... thanks!)
Although I agree with many of the critiques here, I could see this being useful when mocking third-party code you don't control.
I ran into a situation once where I was using a concrete class from the Twilio SDK that didn't have an interface, so I couldn't mock it. If interceptors existed, I could've mocked it just fine.
On that note, I'd be curious as to how deep this can go. Could I intercept some internal code in a third-party library, forcing it to do exactly what I want?
THIS contains an excellent Doug DeMuro reference 😄
Just use interfaces to mock IO abstractions. Use classes if you need to keep track of changes during the lifetime of the object and use statics methods in any other case.
If a branch depends on the return of a static, then your test setup can get very obscure only to be able to cover that specific branch. Combine static with statics, have nested branches, and see your tests exploding. You need to decouple. If you have mostly flat code then using statics is fine, but soon enough branching will crop up.
@@Fred-yq3fs if your code has statics and nested branches and more statics then you need to refactor it.
Have you ever wondered how the Linux kernel works without injecting a single interface?
If the most used system ever built can live without them in sure your API with a call to the dB and return an Ok can live without 236556483338847474 interfaces
@dariogriffo Do you have some article to point out that shows an example of such approach using static methods?
Hmm... What about if the thing that you are intercepting supposed to return different thing on a different test?
This could be very useful to create unit tests of legacy code that doesn't use DI. I wonder placing generated interceptors in generated namespaces and then using for that namespace in the text class file that needs it. Would that allow the separation of interceptors to respond with this different results, as needed by tests? hmm.. not sure.
last video about interceptor I thought to my self, this will be useless for me, now I can see the use in testing, I'm just not sure how useful it will be compared to standard mocking because it is done through extensions which are static, not sure if it will be possible to run tests with different mocks
There's also another problem with your code: if you actually run it, the interceptors will still be called instead of the actual code...
Because you want to generate the code only during test and not for the actual debug/release and get rid of it afterward, that's why Nick told us to assume the code is generated.
@@benomine The problem is that interceptor methods have to be accessible by the intercepted code. So basically, the generated code has to be in your "non-test" dll/exe, whether it's called by your test code or not.
And of course it supposed to be gitignored because of absolute path...
So it seems this have to be generated just before test run and deleted after the test done.
As I understand, only one interceptor may point to the same file,line,char… having two would give you a compile error…
Another problem I see here is that the interceptor will always be overruling the method and thus it will affect the actual binary… so whenever the method is called it will return null!
I expect rider, visual studio, or Roslyn will probably get a validator for if these are mapping correctly, especially if an agreed upon nomenclature arises
I'm not thrilled about Interceptors for test mocking purposes. It feels like too clunky of a system, at least until some serious work is put into doing a code-generating mock framework to create them. BUT then I have to ask what the benefit would be to using interceptors over existing proxy mechanisms? Or even Fody-style code weaving? It's hard for me to see how Interceptors either enable functionality we don't already have OR make improvements over existing functionality (like perf improvements, etc).
Hello Nick, do you know what "gambiarra" means?
So do we need a Carrier then?
Interesting, I was using test harnesses in the 80s that could auto-write interceptors, well procedural 'C'.
I can see this would be useful for legacy, saves creating seams.
To find out how automate this (just not doing this by hand) presumably watch the other video?
the doug demuro thing was gold lmao
Don't you think absolute paths in code are ok? After all, as if we were working together and this code I do not have the same path to the file as you.
Looks great in terms of testing the code where is used third party library or testing legacy code. This feature may be helpful
That Doug DeMuro reference though :D
Wow, that is really great feature for testing! But I want to ask you to use relative paths instead of absolute paths in your videos. It may confusing someone, especially newcomers, who are learning C# And they may wrongly thinking that it is ok to have absolute paths in code, which is obviously not ok.
Unexpected Doug :)
This is very similar to how mocking functions works in python - and is really hard to maintain or understand the test.
This normally shows a problem with the design more than an issue with the testing framework from my experience.
Thank you for explaining this new feature, but IMHO "old-school" mocking is less confusing.
This is just a building block, You are not suposse to test this way.
This is going to need a much better way to target the function call than specifying the file path (not guaranteed to be the same across developer machines) and line numbers and character offsets (likely to change when the target file is modified). This is far too unpolished a solution for me to get excited about or recommend to my teammates.
I totally agree. I like the idea of interceptors, but can see how this will break because of the reasons you´ve just pointed out. Legacy code can be a use case, though.
When line numbers and character offsets change, the attribute on the intercepting code will also change, because it's all generated on the fly, NOT written by a human. All that ugly details should be invisible and "just work" for 99.9% of C# devs.
In corporate world it would be beneficial to have possibility to mock static classes and methods. This is because of "quality gates" which require certain level of code coverage. Because of this people are avoiding static classes. But these interceptors will not help. They require to give exact location in code which should be intercepted. The problem is that different test environments require different path.
This will be tricky since the interceptor is declared once per assembly. Means the interception logic has to be shared amongst all the unit tests in the same project.
Yes: If used for mocking then 1 method must be able to be intercepted by n interceptors. That leaves the problem of knowing which one to use, meaning an interceptor should exist in some sort of context that would make it unique.
If interceptors stay as they are then they could be used as a life-saver, in legacy code where refactor to inject is not a viable option, but that would not be pretty.
Seems crazy brittle. Then again this would be good for legacy code based without unit tests.
yep, maybe if we do not need virtual extends or contract management, interceptor maybe useful for similar cases such as testing. Otherwise, even mappers kill readability, interceptor will have a nuke effect.
In general the idea is very cool, but I suppose it will not be implemented at the nearest future as we'll need to sacrifice the "on-the-fly re-compilation" feature during tests debugging
The interceptor look as an interesting feature in coder's arsenal to resolve the mocking problem. However I noticed one problem. Firstly there was an absolute path mentioned where the extension will be applied and secondly there were mentioned the line of code and column where to do it. The frist time we add a space or a new line if I understood correctly that should not work. On the other side that full absolute path won't be the same for each member of some development team. So we have 2 problems but other than that is a nice feature.
felices san fermines mi gente
I don't know that it's right to say "interfaces are _abused,"_ though they may certainly be _overused._ Not everything _needs_ to be an interface, but I don't know of anything that's _harmed_ be being one.
that DeMuro cameo made me laugh tho
Around the 7 mark I had a brief Fight Club flashback
OOP programmers when they realize they don't need 99% of OOP concepts:
I love interceptors. I understand many people's worries, but I think it's generally overblown.
IDEs will, similar to implicit conversions, give you intellisense whenever they're intercepted.
i think it will make chaos in you legacy code base cause it suppose unit tests in another namespace and will affect i guess
This is the wrong way in my opinion, we should have like special operator called, for example, "interfaceof", so we can extract an interface of a class if it covers all non-static public properties/method. It's cleaner, simpler, less verbose and the most important: intuitive. That would not only simplify mocking of our own services, but most importantly we will be able to abstract from third party services which authors didn't thought about exposing interfaces (today we need some libraries that do assembly magic at runtime).
I would be happe to have this feature.
It's hard to count how many things I've had to fix internally using Xamarin.Forms. So, instead of having the patched XF nuget package everything I need is to fix the wrong method.
It would be nice to have access to private/protected/internal things too.
It would be extreme cool to be able to combine this feature with the Source Link too. Just imagine you have some exception in the third-party library, you debug it using source link, then patch the method in 1-2-3 clicks!
Personally I struggle to see how maintainable code will be that uses this feature. Surely if you add in a new line or change the length of a variable or function name you’ll have to work everything out all over again?
Am I right in thinking that the test will fail when someone visit the class and decide to add a comment line as the interceptor won't be executed properly ?
No. You're not supposed to write that attribute yourself. A source generator should be used to write the interceptor for you.
This does only work for only one usecase. Seems also weird to debug or follow it since its jumping around.
Just give us more evolved constraints, such as the C++ 'Concepts', exposing possibilities by removing possibilities, being able to funnel down into functionalities that become inferable by the compiler.
You can of course mock classes, assuming the methods you want to mock are marked virtual.
Perhaps better than making a god damn interface for every god damn class. :)
Instead of 'goto', now we have 'comeFrom'.
This is so wrong i don't even know where to start.
Why is the static class in the main project and not the test project? Do I need to build my code differently to run tests?
What if I want to return something more interesting than `null` or `true`? Do I need to hardcode the same object in two places (test code and the interceptor generator)?
What if different tests need different values from the mocked classes (e.g. to test how the MovieService reacts to a validation error)?
Just use interfaces. They're simple, obvious, and your IDE can auto-generate one.
Please don't touch my interfaces