Why You Might Not Need Interfaces in C# 12

Поділитися
Вставка
  • Опубліковано 3 гру 2024

КОМЕНТАРІ • 300

  • @phizc
    @phizc Рік тому +81

    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

  • @davew2040x
    @davew2040x Рік тому +2

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

  • @radekzahradnik65
    @radekzahradnik65 Рік тому +4

    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.

  • @KieranDevvs
    @KieranDevvs Рік тому +80

    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 Рік тому +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 Рік тому +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 9 місяців тому

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

  • @DavidZidar
    @DavidZidar Рік тому +98

    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 Рік тому +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 Рік тому +5

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

    • @xeekk
      @xeekk Рік тому +1

      @@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 Рік тому +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.

  • @simenmailundsvendsen9358
    @simenmailundsvendsen9358 Рік тому +109

    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 Рік тому +13

      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 Рік тому +1

      so, don't use it then.

    • @modernkennnern
      @modernkennnern Рік тому +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.

    • @briumphbimbles
      @briumphbimbles Рік тому +1

      @@bondarenkodfUnfortunately some of us write code with other people

  • @aronsz
    @aronsz Рік тому +10

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

  • @andrewjosephsaid788
    @andrewjosephsaid788 Рік тому +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 Рік тому +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

  • @andersborum9267
    @andersborum9267 Рік тому +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.

  • @vorontsovru270895
    @vorontsovru270895 Рік тому +2

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

  • @alphaios7763
    @alphaios7763 Рік тому +3

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

    • @nickchapsas
      @nickchapsas  Рік тому +3

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

  • @michaldivismusic
    @michaldivismusic Рік тому +11

    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 Рік тому +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 Рік тому +1

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

  • @michalkowalik89
    @michalkowalik89 Рік тому +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.

  • @DarraghJones
    @DarraghJones Рік тому +1

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

  • @minnedanhieux1040
    @minnedanhieux1040 6 місяців тому

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

  • @SecondFinale
    @SecondFinale Рік тому +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 Рік тому

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

    • @robwalker4653
      @robwalker4653 Рік тому +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 Рік тому

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

  • @XXnickles
    @XXnickles Рік тому +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.

  • @MaQy
    @MaQy Рік тому +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.

  • @felipet391
    @felipet391 Рік тому

    I am really excited!!

  • @lambda-snail
    @lambda-snail Рік тому +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 Рік тому

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

    • @PticostaricaGS
      @PticostaricaGS Рік тому +1

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

  • @FilipSzymaniak
    @FilipSzymaniak Рік тому

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

  • @kelbobk5
    @kelbobk5 Рік тому +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 Рік тому

      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 Рік тому +1

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

    • @Eddyi0202
      @Eddyi0202 Місяць тому +1

      @@kelbobk5 Honestly I don't see how it's better than using an interface/delegate, it's weird cause looking at this I would assume that I can change what is the current date time in my production code. With an interface and mock I can have clear distinction and only one method instead of 3 and property

  • @DemoricTv
    @DemoricTv Рік тому +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 Рік тому

      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.

  • @dwhxyz
    @dwhxyz Рік тому +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 Рік тому +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  Рік тому +1

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

  • @nickst0ne
    @nickst0ne Рік тому +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 Рік тому

      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 Рік тому +5

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

  • @tajkris
    @tajkris Рік тому +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 Рік тому

      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

  • @HimmDawg
    @HimmDawg Рік тому +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.

  • @officemishler3364
    @officemishler3364 Рік тому +1

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

  • @luvincste
    @luvincste Рік тому +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

  • @JackBauerDev
    @JackBauerDev Рік тому +1

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

  • @brooklyndev
    @brooklyndev Рік тому +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 Рік тому +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 Рік тому

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

  • @jameshancock
    @jameshancock Рік тому +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.

  • @matthias916
    @matthias916 Рік тому +3

    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

  • @dagochen
    @dagochen Рік тому

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

  • @kejansenz
    @kejansenz Рік тому +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?

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

  • @desertfoxmb
    @desertfoxmb Рік тому +2

    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.

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

  • @billy65bob
    @billy65bob Рік тому +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 Рік тому +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 Рік тому

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

    • @logank.70
      @logank.70 Рік тому

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

    • @phizc
      @phizc Рік тому

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

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

    • @AkiraTTS
      @AkiraTTS 23 дні тому +1

      I agree with everything. However even the "legacy code bases" argument is very hard to make, since a legacy code base will probably be miles away from C# 12

  • @christopherwilliams3293
    @christopherwilliams3293 Рік тому +2

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

  • @Exosia
    @Exosia Рік тому +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.

  • @CrIMeFiBeR
    @CrIMeFiBeR Рік тому

    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.

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

  • @ryanseipp6944
    @ryanseipp6944 Рік тому +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 Рік тому

      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.

  • @realsk1992
    @realsk1992 Рік тому

    What kind of Result class do you use?

  • @dkostasx
    @dkostasx Рік тому +2

    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.

  • @zalatos
    @zalatos 4 місяці тому

    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

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

  • @michaelgehling6742
    @michaelgehling6742 Рік тому +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  Рік тому +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 Рік тому

      @@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 Рік тому

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

    • @nickchapsas
      @nickchapsas  Рік тому

      @@michaelgehling6742 Correct

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

  • @MrErnow
    @MrErnow Рік тому +8

    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

  • @CapsAdmin
    @CapsAdmin Рік тому

    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.

  • @ncvjr
    @ncvjr Рік тому +1

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

  • @EarthCitizen124
    @EarthCitizen124 Рік тому

    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  Рік тому +2

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

    • @EarthCitizen124
      @EarthCitizen124 Рік тому

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

  • @sheveksmath702
    @sheveksmath702 Рік тому

    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?

  • @matt-irby
    @matt-irby Рік тому

    THIS contains an excellent Doug DeMuro reference 😄

  • @dariogriffo
    @dariogriffo Рік тому +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 Рік тому

      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 Рік тому +1

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

  • @jchandra74
    @jchandra74 Рік тому

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

  • @codingbloke
    @codingbloke Рік тому

    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.

  • @lordicemaniac
    @lordicemaniac Рік тому +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

  • @Krimog
    @Krimog Рік тому +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 Рік тому +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 Рік тому +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 Рік тому +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.

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

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

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

  • @lucasmicheleto2722
    @lucasmicheleto2722 Рік тому

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

  • @ImmacHn
    @ImmacHn Рік тому

    So do we need a Carrier then?

  • @nickbarton3191
    @nickbarton3191 Рік тому

    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?

  • @1000percent1000
    @1000percent1000 Рік тому

    the doug demuro thing was gold lmao

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

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

  • @the_wilferine
    @the_wilferine Рік тому

    That Doug DeMuro reference though :D

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

  • @VladimirChe80
    @VladimirChe80 Рік тому

    Unexpected Doug :)

  • @talwald1680
    @talwald1680 Рік тому +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.

  • @AndriyBezkorovayny
    @AndriyBezkorovayny Рік тому +1

    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.

  • @countryboyri
    @countryboyri Рік тому +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 Рік тому

      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 Рік тому +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.

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

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

  • @thatcreole9913
    @thatcreole9913 Рік тому +4

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

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

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

  • @MrPatrik775
    @MrPatrik775 3 місяці тому

    The interceptor look as an interesting feature in coder's arsenal to resolve the mocking problem. However I noticed one problem. Firstly there was an absolute path mentioned where the extension will be applied and secondly there were mentioned the line of code and column where to do it. The frist time we add a space or a new line if I understood correctly that should not work. On the other side that full absolute path won't be the same for each member of some development team. So we have 2 problems but other than that is a nice feature.

  • @MrSamisack
    @MrSamisack Рік тому

    felices san fermines mi gente

  • @emerynoel567
    @emerynoel567 Рік тому +1

    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.

  • @qobalt
    @qobalt Рік тому

    that DeMuro cameo made me laugh tho

  • @janhendrikschreier
    @janhendrikschreier Рік тому

    Around the 7 mark I had a brief Fight Club flashback

  • @sr_meia_noite
    @sr_meia_noite Рік тому

    OOP programmers when they realize they don't need 99% of OOP concepts:

  • @modernkennnern
    @modernkennnern Рік тому +3

    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.

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

  • @proosee
    @proosee Рік тому +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).

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

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

  • @garcipat
    @garcipat Рік тому

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

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

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

  • @bradwalker1259
    @bradwalker1259 Рік тому

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

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

  • @antonmartyniuk
    @antonmartyniuk Рік тому

    Please don't touch my interfaces