Testing WITHOUT Mocks or Interfaces!

แชร์
ฝัง
  • เผยแพร่เมื่อ 9 ก.ค. 2024
  • A common approach people take with testing is mocking. Specifically defining interfaces for dependencies which are then typically mocked so you can test in isolation. While interfaces can be helpful for mocking as well as fakes and stubs, there can be other approaches taken. Meaning you don't need to create an interface for everything.
    🔗 EventStoreDB
    eventsto.re/codeopinion
    🔔 Subscribe: / @codeopinion
    💥 Join this channel to get access to a private Discord Server and any source code in my videos.
    🔥 Join via Patreon
    / codeopinion
    ✔️ Join via TH-cam
    / @codeopinion
    📝 Blog: codeopinion.com
    👋 Twitter: / codeopinion
    ✨ LinkedIn: / dcomartin
    📧 Weekly Updates: mailchi.mp/63c7a0b3ff38/codeo...
    0:00 Intro
    1:04 Interface
    4:53 Delegate/Functions
    7:09 Data
    8:10 Level
    #softwarearchitecture #softwaredesign #softwaretesting
  • วิทยาศาสตร์และเทคโนโลยี

ความคิดเห็น • 53

  • @distinguishedmoments2277
    @distinguishedmoments2277 ปีที่แล้ว +35

    These videos are absolute gems in a world filled with rookie videos. Thanks brother

  • @Tony-dp1rl
    @Tony-dp1rl ปีที่แล้ว +19

    Nice video. I think a lot of times developers get trapped into patterns, particularly OOP patterns, and forget how simple some things are with more functional approaches to Dependency Injection.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว +9

      Method injection is also highly underrated and often frowned upon, when I don't think it should be at all.

    • @Knigh7z
      @Knigh7z ปีที่แล้ว

      Classes with static methods are just namespaces for functions. Definitely easy for people to be blinded by OOP.

  • @TheRicherthanyouguy
    @TheRicherthanyouguy ปีที่แล้ว +4

    Since the core concept of this video is discussing other ways to test a piece of code I think it is worth while to mention I’ve had success specifically in making legacy code bases testable or easier to test by using default input parameters. Usually adding a simple conditional to check the value with a default before doing some other bit of logic. It does still require code changes but the changes are usually very small and default values often can prevent the breaking of other preexisting tests. Again I other mention this as one of many options that have had value for me and they seem to align with Derek’s goal of the video so thought I’d share.

  • @nonamezz20
    @nonamezz20 ปีที่แล้ว +3

    What is the benefit of using the delegate over the interface for the date time class ?

  • @jrhodes69
    @jrhodes69 ปีที่แล้ว +3

    For comparing dates against utc now I think the tolerance approach is good enough if sub second accuracy isn’t required ( which is probably most cases). You can make the tolerance big enough so that it would never fail in a unit test as generally the time between the sut being exercised and the assertion being made is very small. Even if it isn’t, you can tweak the tolerance higher as long as it still makes sense in the use case. I’ve used the tolerance approach for a while and I find it a much better and cleaner solution than having to inject extra dependencies just for testing.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว +2

      Date (UtcNow) was just the example of a non-deterministic dependency.

  • @mylesdavies9476
    @mylesdavies9476 ปีที่แล้ว

    This channel is so underrated, great content, thanks

  • @tomheijtink8688
    @tomheijtink8688 ปีที่แล้ว

    It reminds me of a DI system I once made for personal practice. Before I knew about DDD my classes generally only had one public method and no state. But in some cases, I still needed to DI dependencies. So, my delegates didn't always match the signature of the public static method but instead resolved the dependencies via the service provider and registered an action or function which would essentially inject the dependencies into the methods. This worked really well. It was a bit more work to bootstrap. As at that time I was manually resolving the dependencies for some of the method arguments. But I liked the concept.
    This idea removes an interface and introduces a delegate. Which are actually the same, but one is on class level while a delegate is on function level. Which is a bit more to the point in his example.
    So, for the instances where you don't need a set of grouped methods but just only one. A delegate is a good option as it is more to the point and less code to write.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว

      Delegates are severely underused imo. Especially if you have interfaces that their consumers ever only call one method.

  • @Greenthum6
    @Greenthum6 ปีที่แล้ว +6

    I like to follow KISS principle and just use interfaces and stubs in test code. This makes code easily readable for everyone. Mixing delegates into constructors achieves little and adds complexity.
    I think mock code is generally unreadable and use it usually only in cases where the dependency is irrelevant.
    Testing against DateTime.UtcNow is a systemwide challenge, and method parameter injection is bad idea as it needs to be in several places. Again, interface with DI works well.
    HttpClient is IDisposable so injecting it via constructor should be ideally replaced with .NET best practices instead. If there is HttpClient involved an integration test is usually better choice.
    Overall, I like any videos about test automation. No need to nitpick, but hopefully this gives ideas and maybe prove me wrong. Ofc some things are just matter of taste:) I would like to see a video about more realistic app integration testing where these ideas are shown.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว +1

      "Mixing delegates into constructors achieves little and adds complexity." "Again, interface with DI works well."
      We will have to to agree to disagree on this one. 😆 Not sure how injecting a delegate (a function) into a class ctor adds complexity over injecting an interface?
      About method injection, I'm going to assume you don't like the method injection in ASP.NET Core?

    • @Greenthum6
      @Greenthum6 ปีที่แล้ว +4

      @CodeOpinion By added complexity, I mean using two different approaches to enable testing via constructor. If there is a coding convention, then which one to use depending on the case? Does it make writing and reading tests easier?
      Method DI injection works for pure functions, although not a fan. In the video example, passing DateTimeOffset requires the coder to understand what is its purpose. If it's custom delegate, then using it in production code needs always to understand to pass now. It also exposes the method for incorrect creative use.
      I use ITimeProvider interface to provide time everywhere and register impl in DI. Then it is easy to create and test different time patterns like hardcoded, interval stepping or time variations.

    • @EMWMIKE
      @EMWMIKE 10 หลายเดือนก่อน

      httpclient shouldnt be disposed. if you have a high volume flow like creating orders, it will open op new ports and not close them for a couple of minutes. Making you get socket exhaustion

  • @marcozg77
    @marcozg77 ปีที่แล้ว +1

    Thanks for your video and I find it very interesting to see people who are questioning mainstream practices. I'm working as a sw engineer since 1999 and have seen a nice shift from dogmatic to pragmatic in all the years which I personally find liberating (e.g. shifting from "everything is an object because OOP is the only way to go" to "use whatever programming paradigm fits the problem best"). I agree that you don't need an interface for everything. I'm not quite sure whether the first example is good design though: now you expose an implementation detail of the method to the caller, essentially forcing him to decide what to pass. How does he know which datetime value to pass? I don't say it's wrong but it changes the semantics because before, it was part of the method to decide which datetime is used while afterwards, it's up to the caller.
    For me, this looks like something that isn't really something that is that useful to unit test. We have a lot of such methods that are basically just wrapping some insert action, maybe adding some simple transformations. We simply test them "manually" by invoking the respective ui action (for example).
    Ideally, all the real logic (e.g. calculations etc.) is into separate, side-effect free classes, that can easily be unit tested.
    IMHO testing against a method that doesn't do much more than inserting into a repo, by mocking the repo, doesn't really add any value other than to testing that your mock lib is working.
    We do use interfaces for our repos but usually not for mocking reasons; we try to separate the real logic into side-effect ut-testable classes and test the bare crud methods manually.

  • @aneeszuberi1690
    @aneeszuberi1690 8 หลายเดือนก่อน

    I am totally now to testing. this is a great video even I was not able to learn much. I need you guidance, I am working on a project in which I need to create an interface but I have no idea what it is, how to do it etc. Can you guide me where to start and if you can attach a link, that would be great. Thanks

  • @alexandre-pl
    @alexandre-pl ปีที่แล้ว

    The simplest approach to testing is to focus on having pure domain code (free of out-of-process dependencies) which eliminates the need for mocking and then test the application layer through integration tests. The example you shown where the DateTime was provided as a method argument shows how to eliminate out-of-process dependencies, but it wouldn't make sense to have such method contract at the application layer level, where the client is expected to provide inputs.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว

      Yes, as you move outward from the core you're going to have to provide dependencies that likely have side-effects.

  • @MrNuneD
    @MrNuneD ปีที่แล้ว +1

    Great video, just shows there is nothing better than simplicity.
    If using mocks I recommend the library FakeItEasy instead of Moq. Easier to read, setup and cleaner code on the tests.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว

      Yes, I've used FakeItEasy in the past. Good suggestion.

  • @notavailable4891
    @notavailable4891 9 หลายเดือนก่อน

    The problem I have with delegates is it seems similar to not using curly braces on a one line if or immediately returning the result of a function called within another function. In theory, it's nice to keep the code concise but almost inevitably, you need to add code to the if block so now you need to add the curly braces, or you want to debug the result of the function called within the function before it returns or manipulate it in some way. The delegate seems like it will often need to be refactored into a class except unlike my example it will be a pretty big deal if/when it does because you not only need to create the class and interface and the DI for that but then you need to rewrite the test. Idk just sounds like the kinda pain I try to avoid.

  • @dimitryk8429
    @dimitryk8429 ปีที่แล้ว +4

    single approch with injected interfaces is better then mixing in delegates and still using interfaces for all other stuff

  • @martijnveld1053
    @martijnveld1053 ปีที่แล้ว

    Instead of having to create a Mock HttpMessageHandler and do a lot of plumbing yourself in your tests, you can also use the HttpTest class that comes with the excellent Flurl package. Yes it is another set of dependencies to add, but it greatly simplifies the way tests interacting with external services can be set up.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว

      Thanks for the suggestion!

  • @oscareriksson9414
    @oscareriksson9414 ปีที่แล้ว

    Cool! Alternatives are good.

  • @RasmusSchultz
    @RasmusSchultz ปีที่แล้ว +2

    This is fine, but always leaves me longing for language features that make this more easy and natural. Delegates are interface types (you just covered this in a previous video) so there isn't much of a difference - you're still abstracting and mocking. With structural type checking in Typescript, you can substitute anything anywhere, without introducing abstractions. Same in Go. In Dart, you can type-hint using class names, which makes them double as implicit interfaces. I feel like these features have testing in mind. I wish C# would do some reckoning and figure out how to make the language more naturally lend itself to testing without so much plumbing and ceremony - without generating so many artifacts. 🤔

  • @airman122469
    @airman122469 11 หลายเดือนก่อน

    The code I work on right now has dozens of internal interfaces for the purposes of “testing” which it totally insane to me. I hate it. I hate it so much. It’s definitely a design failure.

  • @allinvanguard
    @allinvanguard ปีที่แล้ว +4

    Injecting funcs is a good one. I guess in general, when it comes to what is allowed in testing and mocking, it can be summed up as: Don't change your code in order to make tests works, change your tests in order to make them work with your code. So it's fair game as long as it is not invasive.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว +7

      I'm not so strict on the "Don't change your code in order to make tests works". But I think if something is difficult to test, that might be a sign.

    • @thelonearchitect
      @thelonearchitect ปีที่แล้ว

      I'd add : develop in a testable way. And start with TDD to do so.

    • @kristianaranda
      @kristianaranda ปีที่แล้ว

      Yes, I agree. it's a bit of code pollution. In my humble opinion, the static field could introduce a shared dependency between tests as well.

  • @chriscrossx
    @chriscrossx ปีที่แล้ว

    Thanks for the great content!
    Python has an awesome library called "freezegun" which hooks into system time calls and mocks the date for the whole process. You can essentially freeze the time for the duration of the test or part of the test. Basically a system level mock. We've found it preferable to passing in date arguments which tends to get tedious when you have to pass it all the way down a call stack. I'd be curious to know if you think relying on this kind of system level mocking is a good way to go.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว +1

      Interesting. I'm not immediately against the idea. Ultimately it's about being deterministic as much as you can. Moving things like I/O and side effects up the call stack help with making lower/inner deterministic or as the cool functional kids say, "pure functions". As for a global mocking of a date... I can see this working in some situations but not all.

    • @chriscrossx
      @chriscrossx ปีที่แล้ว

      @@CodeOpinion yeah. There seems to be something special about "now" date handling that makes it less scary to mock in this way. I think you are right to prefer/suggest dependency injection where possible 👍

  • @buriedstpatrick2294
    @buriedstpatrick2294 ปีที่แล้ว +3

    I had a discussion with a colleague who really likes using mocks for services I personally would consider very bloated. I decided to read a bit about some opinions on mocking online and came across the argument that using mocks should really just signal that a specific unit test should be an integration test instead. The more I thought about that argument, the more it made sense to me. I realize this is not always feasible to implement across the board, and sometimes we need to cut corners, but it also made me realize how many of our unit tests aren't really true black box tests. We check that internal calls are being made to mock services, completely violating the encapsulation principle of the SUT.
    Furthermore, changes to the DI hierarchy can break these tests in ways that aren't immediately obvious until you run the test and get a DI-error that isn't relevant to what you're really testing. Whereas if you change a method signature, you'll know immediately as it won't compile until you fix the test. Additionally, if your method requires some external injected dependency to behave in a specific way, maybe the method is doing too much. Why not break it into helper methods that can be individually unit-tested and then run the full method as an integration test? You can even make them static, which makes them the simplest unit to test.
    Anyways, I'm curious on your opinions on it. I know you're not anti-mock, and I wouldn't exactly call myself that either. But it does come off as a crutch to me.

    • @CesarDemi81
      @CesarDemi81 ปีที่แล้ว

      Unit tests are not black boxes. Unit tests are meant to cover every execution path of the method/function (unit) being tested, so because you need to cover those paths to have proper coverage, you HAVE to know what's inside of them, thus not working under the black box concept.

  • @FISS007
    @FISS007 ปีที่แล้ว +1

    The main takeaway is:
    Use whatever works, is clean and saves you time =)
    Thanks for sharing.

  • @Rookiande
    @Rookiande ปีที่แล้ว

    Shouldn't be the title about using manually created fakes/test doubles (mocks/stubs) vs library-created ones?

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว

      If that's the case I didn't get the point across well enough. Fair enough though.

  • @jeffrdrama7984
    @jeffrdrama7984 ปีที่แล้ว +1

    How do I know if this guy knows what he's talking about?

  • @parlor3115
    @parlor3115 ปีที่แล้ว +1

    I'm not sure if this is actually what the video is advocating but why not just isolate all the business logic into pure functions? If you have to load data from the database or from a network call, do it in a topmost function (which is obviously not pure), but then have all that loaded data pass through a series of functions that are pure which transform the initial data or generate new data based on it. Finally, take the resulting data and persist it to the database or send it over the network and that's it. Now you have a perfectly testable set of functions. It's as easy as that I guess.

    • @CodeOpinion
      @CodeOpinion  ปีที่แล้ว +1

      That's pretty much what I leading up to when I was passing in the date to the create order.

    • @eltyo340
      @eltyo340 ปีที่แล้ว +1

      @@CodeOpinion Any F# videos coming up? :p

    • @parlor3115
      @parlor3115 ปีที่แล้ว

      @@CodeOpinion Cool, hopefully more devs start to write code like this. It'll make everyone's lives easier as the code is easier to reason about and ofc easily testable.

    • @sandromagalli8587
      @sandromagalli8587 ปีที่แล้ว

      Sorry im new to programming, can you give me a practical example about passing functions? Do you mean like having a service with a method to save entities like:
      public void SaveEntity(Guid entityId, TSource source, Func getEntity, Action updateEntity)
      {
      var dbContext = _dbContextFactory.Make();
      var entity = getEntity(dbContext, entityId);
      if(entity is null)
      {
      entity = new ();
      dbContext.Add(entity);
      }
      else
      {
      updateEntity(entity, source);
      }
      dbContext.SaveChanges();
      }
      And all the funcs passed as arguments will be in a static class so you call _databaseService.SaveEntity(id, source, DbFuncs.GetEmployee, DbFuncs.UpdateEmployee);

  • @badderborroda4972
    @badderborroda4972 9 หลายเดือนก่อน

    All of these examples are just testing things you've explicitly created in the test - they provide 0 value other than test coverage.

  • @georgehelyar
    @georgehelyar ปีที่แล้ว

    For HttpClient I just use Moq.Mock.Protected. Add a couple of extension methods for convenience and it's as easy to use as any other mock.