No video

Why You Might Not Need Interfaces in C# 12

  Рет қаралды 68,266

Nick Chapsas

Nick Chapsas

Күн бұрын

Check out my courses: dometrain.com
Become a Patreon and get source code access: / nickchapsas
Hello everybody, I'm Nick, and in this video, I will show you a demo of what might be possible if the Interceptors feature of C# 12 actually makes it into the language. One of the biggest problems with C# code is interface bloat for no apparent reason other than just testability.
Interceptor video: • The New “Interceptors”...
Workshops: bit.ly/nickwor...
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: bit.ly/ChapsasG...
Follow me on Twitter: bit.ly/ChapsasT...
Connect on LinkedIn: bit.ly/ChapsasL...
Keep coding merch: keepcoding.shop
#csharp #dotnet

Пікірлер: 295
@phizc
@phizc Жыл бұрын
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.
@officemishler3364
@officemishler3364 Жыл бұрын
Awesome, I was just writing an attribute to do this work for you at run time. Maybe both approaches can be useful
@rafae5902
@rafae5902 Жыл бұрын
Having to update the path/line/column every time the file changes would be insanity.
@KieranDevvs
@KieranDevvs Жыл бұрын
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.
@KieranDevvs
@KieranDevvs Жыл бұрын
@@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.
@modernkennnern
@modernkennnern Жыл бұрын
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
@kRySt4LGaMeR
@kRySt4LGaMeR 6 ай бұрын
Exactly, there's a lot of implicit trickery going on. At least with mocks we know the thing is mocked.
@DavidZidar
@DavidZidar Жыл бұрын
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.
@xeekk
@xeekk Жыл бұрын
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
@AlgoristHQ Жыл бұрын
This is where containerization comes into play. Don’t mock repos! Just used a containerized database!
@xeekk
@xeekk Жыл бұрын
@@AlgoristHQ I use Testcontainers for that so I can keep it playable in visual studio without extra setup.
@AlgoristHQ
@AlgoristHQ Жыл бұрын
@@xeekk I use the built in docker support in vs2022 and docker-compose.
@xeekk
@xeekk Жыл бұрын
@@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.
@aronsz
@aronsz Жыл бұрын
Aren't you advertising the wrong tool for an already solved problem?
@simenmailundsvendsen9358
@simenmailundsvendsen9358 Жыл бұрын
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.
@modernkennnern
@modernkennnern Жыл бұрын
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.
@samsonmayeem8409
@samsonmayeem8409 Жыл бұрын
😢the feature is good, I'm developing a new design principal
@bondarenkodf
@bondarenkodf Жыл бұрын
so, don't use it then.
@modernkennnern
@modernkennnern Жыл бұрын
@@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.
@briumphbimbles
@briumphbimbles Жыл бұрын
@@bondarenkodfUnfortunately some of us write code with other people
@andersborum9267
@andersborum9267 Жыл бұрын
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.
@andrewjosephsaid788
@andrewjosephsaid788 Жыл бұрын
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.
@qj0n
@qj0n Жыл бұрын
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
@michalkowalik89
@michalkowalik89 Жыл бұрын
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.
@vorontsovru270895
@vorontsovru270895 Жыл бұрын
6:41 THIS is a pretty good easter egg =)
@michaldivismusic
@michaldivismusic Жыл бұрын
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?
@ChenfengBao
@ChenfengBao Жыл бұрын
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.
@michaldivismusic
@michaldivismusic Жыл бұрын
@@ChenfengBao alright, that does make it sound much better.
@kocot.
@kocot. Жыл бұрын
ms fakes do the legacy part already without updating to .net 8, end of strory
@radekzahradnik65
@radekzahradnik65 Жыл бұрын
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.
@kocot.
@kocot. Жыл бұрын
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.
@alphaios7763
@alphaios7763 Жыл бұрын
the Doug flash when you said "THIS" made me laugh like crazy!
@nickchapsas
@nickchapsas Жыл бұрын
I'm so glad someone appreciates it. I thought of it during editing and I couldn't stop laughing
@minnedanhieux1040
@minnedanhieux1040 2 ай бұрын
This pretty much replaces one headache with another headache. :)
@kelbobk5
@kelbobk5 Жыл бұрын
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.
@sumeshk2653
@sumeshk2653 Жыл бұрын
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
@chris-pee Жыл бұрын
What's "an updateable static method"?
@kelbobk5
@kelbobk5 Жыл бұрын
@@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; }
@jameshancock
@jameshancock Жыл бұрын
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.
@XXnickles
@XXnickles Жыл бұрын
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.
@qj0n
@qj0n Жыл бұрын
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
@tajkris
@tajkris Жыл бұрын
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.
@qj0n
@qj0n Жыл бұрын
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
@dwhxyz
@dwhxyz Жыл бұрын
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.
@dwhxyz
@dwhxyz Жыл бұрын
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.
@nickchapsas
@nickchapsas Жыл бұрын
I have never heard of any of this, I really need to look into it
@lambda-snail
@lambda-snail Жыл бұрын
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 :)
@squadwuschel
@squadwuschel Жыл бұрын
Yes I am also using virtual methods to run unit tests and don't use interfaces
@PticostaricaGS
@PticostaricaGS Жыл бұрын
I think interceptors may be very useful in code self-analysis and something in the lines of memory inspection and performance-counters alike
@MaQy
@MaQy Жыл бұрын
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.
@DarraghJones
@DarraghJones Жыл бұрын
This will be a real life saver for my legacy C# 12 apps 😬
@officemishler3364
@officemishler3364 Жыл бұрын
I made it as far as providing the line number and character location before deciding this is worse than mocking.
@MrErnow
@MrErnow Жыл бұрын
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.
@paulkoopmans4620
@paulkoopmans4620 Жыл бұрын
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.
@diadetediotedio6918
@diadetediotedio6918 Жыл бұрын
> Just don't use
@StephenZura
@StephenZura Жыл бұрын
If it requires a static path that will never change, that's already a failure point. I have yet to meet a team of developers that use the exact same pathing across their systems to store their code. On top of that, Line and column definition seems incredibly clunky and prone to breaking.
@phizc
@phizc Жыл бұрын
You're not supposed to write the interceptors, or at least that attribute yourself. You're supposed to use source generators, and they'll update the path/line/column if anything changes.
@SecondFinale
@SecondFinale Жыл бұрын
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
@qj0n Жыл бұрын
Well, if it's not then you probably will have troubles with mocking it anyway
@robwalker4653
@robwalker4653 Жыл бұрын
@@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.
@qj0n
@qj0n Жыл бұрын
@@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
@Krimog
@Krimog Жыл бұрын
There's also another problem with your code: if you actually run it, the interceptors will still be called instead of the actual code...
@benomine
@benomine Жыл бұрын
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.
@Krimog
@Krimog Жыл бұрын
@@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.
@AnatholyBonder
@AnatholyBonder Жыл бұрын
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.
@christopherwilliams3293
@christopherwilliams3293 Жыл бұрын
I would not go this route, but I do wish C# had a good level of AOP support like Java.
@DemoricTv
@DemoricTv Жыл бұрын
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?
@GeriatricMillenial
@GeriatricMillenial Жыл бұрын
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.
@proosee
@proosee Жыл бұрын
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).
@HimmDawg
@HimmDawg Жыл бұрын
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.
@nickst0ne
@nickst0ne Жыл бұрын
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.
@akiwoo5205
@akiwoo5205 Жыл бұрын
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.
@therealantiarchitect
@therealantiarchitect Жыл бұрын
Legacy code bases have a problem though. You'd have to upgrade them to .Net 8 first. 🤔
@countryboyri
@countryboyri Жыл бұрын
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.
@camlcase
@camlcase Жыл бұрын
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.
@ChenfengBao
@ChenfengBao Жыл бұрын
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.
@zalatos
@zalatos 21 күн бұрын
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
@kejansenz
@kejansenz Жыл бұрын
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?
@davew2040x
@davew2040x Жыл бұрын
Oh please God, don’t tell me that Nick is jumping on the interceptor train
@z0nx
@z0nx Жыл бұрын
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?
@FilipSzymaniak
@FilipSzymaniak Жыл бұрын
interesting feature, going to check interceptors out soon, thanks!
@JackBauerDev
@JackBauerDev Жыл бұрын
Mock frameworks still have a lot more functionality, just for example, verify.
@brooklyndev
@brooklyndev Жыл бұрын
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.
@ronsijm
@ronsijm Жыл бұрын
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
@JamesOfKS
@JamesOfKS Жыл бұрын
@@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.
@dariogriffo
@dariogriffo Жыл бұрын
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.
@Fred-yq3fs
@Fred-yq3fs Жыл бұрын
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.
@dariogriffo
@dariogriffo Жыл бұрын
@@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
@Eddyi0202
@Eddyi0202 Жыл бұрын
@dariogriffo Do you have some article to point out that shows an example of such approach using static methods?
@CapsAdmin
@CapsAdmin 11 ай бұрын
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.
@Exosia
@Exosia Жыл бұрын
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.
@porterneon
@porterneon Жыл бұрын
Change or add one line to tested class and references by column and row number will fail. THis could be useful only if tested class will not be modified in the future.
@tomlee7073
@tomlee7073 Жыл бұрын
I was just about to post the exact same thing
@MrMeGaSeNt
@MrMeGaSeNt Жыл бұрын
Sound like a monkey patching
@desertfoxmb
@desertfoxmb Жыл бұрын
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.
@matt-irby
@matt-irby Жыл бұрын
THIS contains an excellent Doug DeMuro reference 😄
@dkostasx
@dkostasx Жыл бұрын
This could be really useful when working with some old code which because of its complexity cannot be easily refactored
@rafae5902
@rafae5902 Жыл бұрын
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.
@MrSamisack
@MrSamisack Жыл бұрын
felices san fermines mi gente
@1000percent1000
@1000percent1000 Жыл бұрын
the doug demuro thing was gold lmao
@robsosno
@robsosno Жыл бұрын
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.
@ryanseipp6944
@ryanseipp6944 Жыл бұрын
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...
@phizc
@phizc Жыл бұрын
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.
@JesperH
@JesperH Жыл бұрын
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!
@modernkennnern
@modernkennnern Жыл бұрын
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.
@jchandra74
@jchandra74 Жыл бұрын
Hmm... What about if the thing that you are intercepting supposed to return different thing on a different test?
@ncvjr
@ncvjr Жыл бұрын
Probably the first time that I not agree your approach. It’s weird. The tracking of mock is a phantom in the code. :/
@luvincste
@luvincste Жыл бұрын
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
@rollingc2013
@rollingc2013 Жыл бұрын
This is like using a rocket launcher to kill a mosquito. A better example I think is to use of Func or Actionn to inject behaviour, that can be a better alternative than passing in interfaces.
@vitek0585
@vitek0585 Жыл бұрын
Looks great in terms of testing the code where is used third party library or testing legacy code. This feature may be helpful
@felipet391
@felipet391 Жыл бұрын
I am really excited!!
@Bliss467
@Bliss467 Жыл бұрын
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
@pilotboba
@pilotboba Жыл бұрын
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. :)
@srivathsaharishvenk
@srivathsaharishvenk Жыл бұрын
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
@the_wilferine
@the_wilferine Жыл бұрын
That Doug DeMuro reference though :D
@heyyrudyy404
@heyyrudyy404 Жыл бұрын
This looks like event-driven testing capabilities. The process of testing emits event and other part of code intercept event and handle it.
@jeroen7362
@jeroen7362 7 ай бұрын
Yeah, i may be an outcast for my opinion but there are a few guides i have for tests. when they are more complicated then your code they are making things worse. they would need a test for the test! A test should really test something and not just hit mocks. testing crud without any validators in it, is just testing if your database works but then without using a database? you test nothing! But if you do have a validator you have a good case to write a test, make sure in your design that you can call that validator without using mocks, a validator should need no database, i would be just a bunch of logic, like not null and not longer then, not containing invalid stuff etc. If it needs data from a location then just give that data as parameter so it could be a static validator that is something you probably can test without the need of mocks. So write tests to actually test something and not to hit a line of code just for the metrics
@haxi52
@haxi52 Жыл бұрын
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.
@Fred-yq3fs
@Fred-yq3fs Жыл бұрын
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.
@AndriyBezkorovayny
@AndriyBezkorovayny Жыл бұрын
Thank you for explaining this new feature, but IMHO "old-school" mocking is less confusing.
@neociber24
@neociber24 Жыл бұрын
This is just a building block, You are not suposse to test this way.
@gdargdar91
@gdargdar91 Жыл бұрын
Type classes are the replacement for interfaces, not interceptors.
@nickchapsas
@nickchapsas Жыл бұрын
Interfaces don’t need a replacement. Many of them just don’t need to exist
@futurexjam2
@futurexjam2 Жыл бұрын
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.
@notpingu
@notpingu Жыл бұрын
It's a bit of an assumption that interfaces are used to support mocking in the first place. Personally, I find that the third most important reason. The more important ones are: 1) enforcing loose coupling, 2) allowing multiple implementations, which has come in handy a lot of times. For example we have a multi-tenant system where each tenant gets their own database and some tenants are on old versions of the database. By exposing repositories as an interface, we can easily support multiple implementations corresponding to the tentant's database version.
@nickchapsas
@nickchapsas Жыл бұрын
It's not an assumption. It's very much a fact on how C# developers write code. Just having an interface for something doesn't mean you have loose coupling. Just allowing for something to happen doesn't mean that you will actually ever need to have multiple implementations
@matthewwood4756
@matthewwood4756 Жыл бұрын
@@nickchapsas developing to an interface/abstraction instead of an implementation was/is not about mocking/testing. It’s just evolved to become the first thought/preference of most “developers” without them really understanding their original intent/use. Interfaces/abstractions” were designed/intended around the paradigm of designing/developing code that isn’t tightly bound to implementation specific details, as well as help overcome shortcomings of single inheritance. The concept of them has been around long before C#.
@notpingu
@notpingu Жыл бұрын
@@nickchapsas Stating it to be a fact doesn't make it so. Interfaces are useful in implementing 2 of the SOLID principles. Unit testing/mocking doesn't even come into play there yet.
@mAcCoLo666
@mAcCoLo666 Жыл бұрын
I agree with Nick. 9 out of 10 people will create `ISomething` just for the sake of testing and only ever have a single implementation. That's still pretty tightly coupled if you ask me. The real treat would be for C# to start supporting some sort of "automatic" interface syntax so that every class will by default be mockable when using some naming convention. So say that you have your `Something` class and in your test you start mocking `ISomething`. Then the compiler could generate that `ISomething` for you automatically without you having to create an explicit interface for your class, as your class public contract _already defines_ the interface.
@matthewwood4756
@matthewwood4756 Жыл бұрын
@@mAcCoLo666 - your comment highlights my point. So many “developers” really don’t understand the fundamental concept of what interfaces/abstractions actually are and their original intent/why they’re used. Their original inception has been around for a few decades now.
@Kwpolska
@Kwpolska Жыл бұрын
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.
@wknight8111
@wknight8111 Жыл бұрын
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).
@emerynoel567
@emerynoel567 Жыл бұрын
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.
@azalpacir
@azalpacir Жыл бұрын
The keyword here is “might”. It’s amazing but a no for me. Easily breaks when a new line of code is added but should be usable in certain scenarios.
@djupstaten2328
@djupstaten2328 Жыл бұрын
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.
@sizenineelm
@sizenineelm 11 ай бұрын
Why would you use an interceptor for testing when an interface with mocking already solves the problem with a well-recognised pattern? Individual tests can set up the calls/results without needing multiple interceptors defined for the same calls. This appears to be a solution trying to solve the wrong problem.
@talwald1680
@talwald1680 Жыл бұрын
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.
@NickBullCSharp
@NickBullCSharp Жыл бұрын
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 🤪
@marcusmajarra
@marcusmajarra Жыл бұрын
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.
@bondarenkodf
@bondarenkodf Жыл бұрын
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!
@ApacheGamingUK
@ApacheGamingUK Жыл бұрын
This all reminds me so much of Harmony patching. It doesn't need to be in the main assembly, it doesn't require any changes to main assembly code; no attributes, partial classes, or any kind of markup at all. It can even help to "mock" (patch/substitute) internal, and private scope classes, and class members, without any "InternalsVisibleTo" additions. You can also cherry-pick implementations, by transpiling them, to substitute/mock block scope variables. Harmony has been used in the world of .NET game development for years, and has a huge audience. It's nice to see a form of monkey patching being implemented into the framework, but much like Newtonsoft, someone beat them to the punch a loooooong time ago. With any luck, Andreas Padeike might end up going the same way as JamesNK, and being offered a job at MS, to bring Harmony into the fold. harmony.pardeike.net/ Harmony is an incredible tool to work with. It's AccessTools, for working with Reflection are exemplary, and very highly optimised. I've made a NuGet package that harnesses the power of Harmony to make is very easy to work with reflection: www.nuget.org/packages/ApacheTech.Common.Extensions.Harmony github.com/ApacheTech-Nuget-Packages/ApacheTech.Common.Extensions.Harmony
@janhendrikschreier
@janhendrikschreier Жыл бұрын
Around the 7 mark I had a brief Fight Club flashback
@qobalt
@qobalt Жыл бұрын
that DeMuro cameo made me laugh tho
@pedrosilva1437
@pedrosilva1437 Жыл бұрын
The fact that you need the interceptors in the source project is a deal breaker for testing purposes (even if it is source generated). This pollutes the shipping library with test artifacts, which is worse than the original problem you were trying to fix. If this generated interceptor code could live in another assembly that was just used for testing and not shipped, then maybe. (or generated into the test project itself.)
@VladimirCheTV
@VladimirCheTV Жыл бұрын
Unexpected Doug :)
@ImmacHn
@ImmacHn Жыл бұрын
So do we need a Carrier then?
@Termit2009
@Termit2009 Жыл бұрын
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
@garcipat
@garcipat Жыл бұрын
This does only work for only one usecase. Seems also weird to debug or follow it since its jumping around.
@nelakendra2296
@nelakendra2296 Жыл бұрын
Something that would actually help reduce interfaces would be discriminated unions if they ever get around to implementing them
@realsk1992
@realsk1992 Жыл бұрын
What kind of Result class do you use?
@piotrus5457
@piotrus5457 Жыл бұрын
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.
@ahmedalirefaey3219
@ahmedalirefaey3219 Жыл бұрын
i think it will make chaos in you legacy code base cause it suppose unit tests in another namespace and will affect i guess
@matthias916
@matthias916 Жыл бұрын
cant you mock concrete classes using moq (or other mocking libraries) already?
@qj0n
@qj0n Жыл бұрын
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.
@kocot. Жыл бұрын
you can and it's even a recommended approach for Azure SDK, the method simply need's to be virtual, not a big deal
@qj0n
@qj0n Жыл бұрын
@@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
@ghaf222
@ghaf222 Жыл бұрын
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?
@elpe21
@elpe21 Жыл бұрын
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 ?
@phizc
@phizc Жыл бұрын
No. You're not supposed to write that attribute yourself. A source generator should be used to write the interceptor for you.
@sergiik2168
@sergiik2168 Жыл бұрын
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.
@Lactorioga
@Lactorioga Жыл бұрын
no Nick, this is not a viable thing - any file change require changing location. Even if they introduce attribute like [InterceptorMark("blaBlaBla")] - i don't know what is worse
@nickchapsas
@nickchapsas Жыл бұрын
Ofc it's viable. The interceptors are code generated. It doesn't matter if the location changes
@AaronShumaker
@AaronShumaker Жыл бұрын
I've always disliked interfaces used just to support unit testing and DI. It's not what where their value is, and is only used there as a crutch for lack of a better mechanism. If your interfaces always have exactly one runtime implementation, then you're not really using them where they are beneficial. This makes it really hard to recognize when an architecture is using an interface for more than just to support DI, and it becomes obtrusive to decompose types into proper interfaces. Juval Lowy has some old talks on decomposing interfaces that I suggest everyone watch.
@vasilymeleshko
@vasilymeleshko Жыл бұрын
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?
@phizc
@phizc Жыл бұрын
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.
Collections Just Changed in C# 12 and That’s Good
8:01
Nick Chapsas
Рет қаралды 105 М.
"I Lost a Job Because of This Codebase"
14:08
Nick Chapsas
Рет қаралды 65 М.
Running With Bigger And Bigger Feastables
00:17
MrBeast
Рет қаралды 159 МЛН
Joker can't swim!#joker #shorts
00:46
Untitled Joker
Рет қаралды 40 МЛН
When you discover a family secret
00:59
im_siowei
Рет қаралды 14 МЛН
黑天使遇到什么了?#short #angel #clown
00:34
Super Beauty team
Рет қаралды 47 МЛН
The 3 Biggest Mistakes of Object Mapping in .NET
11:33
Nick Chapsas
Рет қаралды 63 М.
6 C# Mistakes Microsoft Wants You to Fix
12:57
Nick Chapsas
Рет қаралды 61 М.
Stop Using IEnumerable The Wrong Way in .NET! | Code Cop #019
10:10
3 .NET "Best Practices" I Changed My Mind About
10:16
Nick Chapsas
Рет қаралды 102 М.
KMP vs. Flutter - Who Will Win The Cross-Platform Battle?
16:19
Philipp Lackner
Рет қаралды 43 М.
Interfaces vs Abstract Classes
14:43
Raw Coding
Рет қаралды 14 М.
Wait... PostgreSQL can do WHAT?
20:33
The Art Of The Terminal
Рет қаралды 193 М.
How Senior Programmers ACTUALLY Write Code
13:37
Thriving Technologist
Рет қаралды 1,5 МЛН
The High Performance Types You Ignored for Years in .NET
10:14
Nick Chapsas
Рет қаралды 45 М.
Running With Bigger And Bigger Feastables
00:17
MrBeast
Рет қаралды 159 МЛН