Why You Might Not Need Interfaces in C# 12

Поділитися
Вставка
  • Опубліковано 5 лип 2023
  • 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/nickworkshops
    Don't forget to comment, like and subscribe :)
    Social Media:
    Follow me on GitHub: bit.ly/ChapsasGitHub
    Follow me on Twitter: bit.ly/ChapsasTwitter
    Connect on LinkedIn: bit.ly/ChapsasLinkedIn
    Keep coding merch: keepcoding.shop
    #csharp #dotnet

КОМЕНТАРІ • 294

  • @phizc
    @phizc 11 місяців тому +72

    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 11 місяців тому

      Awesome, I was just writing an attribute to do this work for you at run time. Maybe both approaches can be useful

    • @rafae5902
      @rafae5902 11 місяців тому +4

      Having to update the path/line/column every time the file changes would be insanity.

  • @KieranDevvs
    @KieranDevvs 11 місяців тому +79

    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
      @tcortega 11 місяців тому +1

      It is necessary. Usually, we'd use mock libraries but these mock libraries would be most likely source generators that create interceptors behind the scenes. But I feel you, it feels weird and overengineered.

    • @KieranDevvs
      @KieranDevvs 11 місяців тому +2

      @@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 11 місяців тому +2

      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 3 місяці тому

      Exactly, there's a lot of implicit trickery going on. At least with mocks we know the thing is mocked.

  • @DavidZidar
    @DavidZidar 11 місяців тому +93

    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 11 місяців тому +5

      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 11 місяців тому +5

      This is where containerization comes into play. Don’t mock repos! Just used a containerized database!

    • @xeekk
      @xeekk 11 місяців тому +1

      @@AlgoristHQ I use Testcontainers for that so I can keep it playable in visual studio without extra setup.

    • @AlgoristHQ
      @AlgoristHQ 11 місяців тому

      @@xeekk I use the built in docker support in vs2022 and docker-compose.

    • @xeekk
      @xeekk 11 місяців тому +2

      @@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 11 місяців тому +5

    Aren't you advertising the wrong tool for an already solved problem?

  • @andersborum9267
    @andersborum9267 10 місяців тому +6

    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.

  • @michalkowalik89
    @michalkowalik89 11 місяців тому +6

    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.

  • @andrewjosephsaid788
    @andrewjosephsaid788 11 місяців тому +3

    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 11 місяців тому +1

      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

  • @alphaios7763
    @alphaios7763 11 місяців тому +3

    the Doug flash when you said "THIS" made me laugh like crazy!

    • @nickchapsas
      @nickchapsas  11 місяців тому +3

      I'm so glad someone appreciates it. I thought of it during editing and I couldn't stop laughing

  • @michaldivismusic
    @michaldivismusic 11 місяців тому +10

    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 11 місяців тому +1

      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 11 місяців тому +1

      @@ChenfengBao alright, that does make it sound much better.

    • @kocot.
      @kocot. 10 місяців тому

      ms fakes do the legacy part already without updating to .net 8, end of strory

  • @minnedanhieux1040
    @minnedanhieux1040 9 днів тому

    This pretty much replaces one headache with another headache. :)

  • @vorontsovru270895
    @vorontsovru270895 11 місяців тому +2

    6:41 THIS is a pretty good easter egg =)

  • @XXnickles
    @XXnickles 11 місяців тому +5

    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.

  • @FilipSzymaniak
    @FilipSzymaniak 11 місяців тому

    interesting feature, going to check interceptors out soon, thanks!

  • @felipet391
    @felipet391 11 місяців тому

    I am really excited!!

  • @MaQy
    @MaQy 11 місяців тому +3

    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.

  • @kelbobk5
    @kelbobk5 11 місяців тому +4

    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 11 місяців тому

      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 11 місяців тому +1

      What's "an updateable static method"?

    • @kelbobk5
      @kelbobk5 10 місяців тому

      @@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;
      }

  • @tajkris
    @tajkris 11 місяців тому +5

    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 11 місяців тому

      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

  • @lambda-snail
    @lambda-snail 11 місяців тому +5

    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 11 місяців тому

      Yes I am also using virtual methods to run unit tests and don't use interfaces

    • @PticostaricaGS
      @PticostaricaGS 11 місяців тому +1

      I think interceptors may be very useful in code self-analysis and something in the lines of memory inspection and performance-counters alike

  • @dagochen
    @dagochen 11 місяців тому

    @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.

  • @DarraghJones
    @DarraghJones 11 місяців тому

    This will be a real life saver for my legacy C# 12 apps 😬

  • @qj0n
    @qj0n 11 місяців тому

    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

  • @Krimog
    @Krimog 11 місяців тому +9

    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 11 місяців тому +2

      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 11 місяців тому +3

      @@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 11 місяців тому +2

      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.

  • @marcusmajarra
    @marcusmajarra 11 місяців тому

    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.

  • @DemoricTv
    @DemoricTv 11 місяців тому +2

    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 11 місяців тому

      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.

  • @simenmailundsvendsen9358
    @simenmailundsvendsen9358 11 місяців тому +100

    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 11 місяців тому +12

      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 11 місяців тому

      😢the feature is good, I'm developing a new design principal

    • @bondarenkodf
      @bondarenkodf 11 місяців тому +1

      so, don't use it then.

    • @modernkennnern
      @modernkennnern 11 місяців тому +1

      @@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.

    • @womp6338
      @womp6338 10 місяців тому +2

      Dependency injection is magic as well. There’s lots of magic stuff happening that you are required to know about in most code bases

  • @luvincste
    @luvincste 11 місяців тому +1

    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

  • @radekzahradnik65
    @radekzahradnik65 11 місяців тому +2

    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. 10 місяців тому

      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.

  • @HimmDawg
    @HimmDawg 11 місяців тому +1

    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.

  • @CrIMeFiBeR
    @CrIMeFiBeR 11 місяців тому

    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.

  • @dwhxyz
    @dwhxyz 11 місяців тому +2

    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 11 місяців тому +2

      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  11 місяців тому +1

      I have never heard of any of this, I really need to look into it

  • @vasilymeleshko
    @vasilymeleshko 11 місяців тому

    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 11 місяців тому

      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.

  • @NickBullCSharp
    @NickBullCSharp 10 місяців тому

    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 🤪

  • @officemishler3364
    @officemishler3364 11 місяців тому +1

    I made it as far as providing the line number and character location before deciding this is worse than mocking.

  • @brooklyndev
    @brooklyndev 11 місяців тому +1

    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 11 місяців тому +2

      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 10 місяців тому

      @@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.

  • @lordicemaniac
    @lordicemaniac 11 місяців тому +1

    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

  • @nickbarton3191
    @nickbarton3191 11 місяців тому

    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?

  • @vitek0585
    @vitek0585 11 місяців тому

    Looks great in terms of testing the code where is used third party library or testing legacy code. This feature may be helpful

  • @EarthCitizen124
    @EarthCitizen124 11 місяців тому

    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?)

    • @nickchapsas
      @nickchapsas  11 місяців тому +2

      It's all auto generated. You don't have to write the location, line or character by hand

    • @EarthCitizen124
      @EarthCitizen124 11 місяців тому

      ​@@nickchapsasin this case it makes sense... thanks!)

  • @JVimes
    @JVimes 11 місяців тому +1

    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 11 місяців тому

      Well, if it's not then you probably will have troubles with mocking it anyway

    • @robwalker4653
      @robwalker4653 11 місяців тому +1

      @@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 11 місяців тому

      @@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

  • @michaelgehling6742
    @michaelgehling6742 11 місяців тому +1

    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
      @nickchapsas  11 місяців тому +6

      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

    • @okmarshall
      @okmarshall 11 місяців тому

      @@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!)

    • @michaelgehling6742
      @michaelgehling6742 11 місяців тому

      @@nickchapsas So the code generator will keep track of the code locations I want to "mock"?

    • @nickchapsas
      @nickchapsas  11 місяців тому

      @@michaelgehling6742 Correct

  • @sheveksmath702
    @sheveksmath702 11 місяців тому

    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?

  • @billy65bob
    @billy65bob 11 місяців тому +1

    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.

    • @logank.70
      @logank.70 11 місяців тому +1

      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.

    • @billy65bob
      @billy65bob 11 місяців тому

      @@logank.70 Interesting. I was aware of the RelayCommandAttribute, but not the weird logic for 'p_' names.

    • @logank.70
      @logank.70 11 місяців тому

      @billy65bob they didn't go that route unfortunately. I just wish they had done things like that.

    • @phizc
      @phizc 11 місяців тому

      @@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.

  • @Bliss467
    @Bliss467 11 місяців тому

    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

  • @codingbloke
    @codingbloke 11 місяців тому

    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.

  • @jchandra74
    @jchandra74 11 місяців тому

    Hmm... What about if the thing that you are intercepting supposed to return different thing on a different test?

  • @nickst0ne
    @nickst0ne 11 місяців тому +1

    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 11 місяців тому

      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 11 місяців тому +4

      Legacy code bases have a problem though. You'd have to upgrade them to .Net 8 first. 🤔

  • @christopherwilliams3293
    @christopherwilliams3293 11 місяців тому +2

    I would not go this route, but I do wish C# had a good level of AOP support like Java.

  • @kejansenz
    @kejansenz 11 місяців тому +1

    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?

  • @realsk1992
    @realsk1992 11 місяців тому

    What kind of Result class do you use?

  • @jameshancock
    @jameshancock 11 місяців тому +2

    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.

  • @dkostasx
    @dkostasx 11 місяців тому +2

    This could be really useful when working with some old code which because of its complexity cannot be easily refactored

    • @rafae5902
      @rafae5902 11 місяців тому

      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.

  • @ryanseipp6944
    @ryanseipp6944 11 місяців тому +1

    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 11 місяців тому

      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.

  • @srivathsaharishvenk
    @srivathsaharishvenk 11 місяців тому

    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

  • @matthias916
    @matthias916 11 місяців тому +3

    cant you mock concrete classes using moq (or other mocking libraries) already?

    • @qj0n
      @qj0n 11 місяців тому

      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. 10 місяців тому

      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 10 місяців тому

      @@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

  • @Termit2009
    @Termit2009 11 місяців тому

    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

  • @futurexjam2
    @futurexjam2 11 місяців тому

    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.

  • @JesperH
    @JesperH 11 місяців тому

    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!

  • @Exosia
    @Exosia 11 місяців тому +1

    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.

  • @davew2040x
    @davew2040x 11 місяців тому

    Oh please God, don’t tell me that Nick is jumping on the interceptor train

  • @CapsAdmin
    @CapsAdmin 8 місяців тому

    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.

  • @desertfoxmb
    @desertfoxmb 11 місяців тому +1

    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.

  • @piotrus5457
    @piotrus5457 10 місяців тому

    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.

  • @lucasmicheleto2722
    @lucasmicheleto2722 11 місяців тому

    Hello Nick, do you know what "gambiarra" means?

  • @countryboyri
    @countryboyri 11 місяців тому +15

    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 11 місяців тому

      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 11 місяців тому +2

      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.

  • @1000percent1000
    @1000percent1000 9 місяців тому

    the doug demuro thing was gold lmao

  • @ImmacHn
    @ImmacHn 10 місяців тому

    So do we need a Carrier then?

  • @haxi52
    @haxi52 11 місяців тому

    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 11 місяців тому

      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.

  • @wknight8111
    @wknight8111 11 місяців тому

    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).

  • @JackBauerDev
    @JackBauerDev 11 місяців тому +1

    Mock frameworks still have a lot more functionality, just for example, verify.

  • @ncvjr
    @ncvjr 11 місяців тому +1

    Probably the first time that I not agree your approach. It’s weird. The tracking of mock is a phantom in the code. :/

  • @talwald1680
    @talwald1680 11 місяців тому +1

    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.

  • @z0nx
    @z0nx 11 місяців тому

    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?

  • @the_wilferine
    @the_wilferine 11 місяців тому

    That Doug DeMuro reference though :D

  • @elpe21
    @elpe21 11 місяців тому

    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 11 місяців тому

      No. You're not supposed to write that attribute yourself. A source generator should be used to write the interceptor for you.

  • @VladimirCheTV
    @VladimirCheTV 11 місяців тому

    Unexpected Doug :)

  • @MrSamisack
    @MrSamisack 11 місяців тому

    felices san fermines mi gente

  • @bondarenkodf
    @bondarenkodf 11 місяців тому

    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!

  • @matt-irby
    @matt-irby 11 місяців тому

    THIS contains an excellent Doug DeMuro reference 😄

  • @rvrunkillyow716
    @rvrunkillyow716 9 місяців тому

    it is pretty cool feature.❤

  • @dariogriffo
    @dariogriffo 11 місяців тому +8

    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 11 місяців тому

      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 11 місяців тому

      @@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 11 місяців тому +1

      @dariogriffo Do you have some article to point out that shows an example of such approach using static methods?

  • @janhendrikschreier
    @janhendrikschreier 11 місяців тому

    Around the 7 mark I had a brief Fight Club flashback

  • @qobalt
    @qobalt 11 місяців тому

    that DeMuro cameo made me laugh tho

  • @pilotboba
    @pilotboba 11 місяців тому

    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. :)

  • @robsosno
    @robsosno 11 місяців тому

    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.

  • @AndriyBezkorovayny
    @AndriyBezkorovayny 11 місяців тому +1

    Thank you for explaining this new feature, but IMHO "old-school" mocking is less confusing.

    • @neociber24
      @neociber24 11 місяців тому

      This is just a building block, You are not suposse to test this way.

  • @rollingc2013
    @rollingc2013 10 місяців тому

    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.

  • @ahmedalirefaey3219
    @ahmedalirefaey3219 11 місяців тому

    i think it will make chaos in you legacy code base cause it suppose unit tests in another namespace and will affect i guess

  • @Eddyi0202
    @Eddyi0202 11 місяців тому +1

    There is always an option to use delegate instead of interface

  • @heyyrudyy404
    @heyyrudyy404 9 місяців тому

    This looks like event-driven testing capabilities. The process of testing emits event and other part of code intercept event and handle it.

  • @ghaf222
    @ghaf222 9 місяців тому

    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?

  • @thatcreole9913
    @thatcreole9913 11 місяців тому +4

    Seems crazy brittle. Then again this would be good for legacy code based without unit tests.

  • @djupstaten2328
    @djupstaten2328 11 місяців тому

    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.

  • @proosee
    @proosee 11 місяців тому +1

    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).

  • @garcipat
    @garcipat 9 місяців тому

    This does only work for only one usecase. Seems also weird to debug or follow it since its jumping around.

  • @Thorarin
    @Thorarin 11 місяців тому

    So, basically analternative to Microsoft Fakes? I think that uses profiling APIs to intercept calls. Having this capability without the Enterprise license pay wall might be nice 🙂

  • @jeroen7362
    @jeroen7362 5 місяців тому

    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

  • @bradwalker1259
    @bradwalker1259 11 місяців тому

    Instead of 'goto', now we have 'comeFrom'.

  • @eriknyk2k
    @eriknyk2k 11 місяців тому

    this is very similar when you're working with aspect-oriented programming

  • @sergiik2168
    @sergiik2168 10 місяців тому

    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.

  • @porterneon
    @porterneon 11 місяців тому +2

    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 11 місяців тому

      I was just about to post the exact same thing