Mocking Rust 🤪 and Testing 🧪

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

КОМЕНТАРІ •

  • @peter9477
    @peter9477 2 роки тому +16

    Love the nonchalant making of a function representing a "long-running calculation that results in the answer 42", with no further explanation than that. ;-)

  • @calvinlucian387
    @calvinlucian387 2 роки тому +37

    As a newbie to Rust and a strong supporter of TDD this is probably the best byte-sized intro to testing on UA-cam today imo. Thanks a lot!

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

    Thanks, this was useful. The "mockall" crate seems to be added to "dependencies". But surely that crate shouldn't be included when you compile. If you add it to "dev-dependencies", however, you can't then include "[automock]" in the non-test code. How does this aspect work?

  • @r31527
    @r31527 Рік тому +5

    You're awesome! I've been procrastinating reading the Rust testing docs & you taught me everything I need to get started :)

  • @0xccd
    @0xccd 2 роки тому +12

    Perfect timing. I've been struggling with mocks in Rust. Thanks!

    • @codetothemoon
      @codetothemoon  2 роки тому +1

      Glad this video could help! Mocking in Rust is definitely a bit tricky.

  • @Mustafa-099
    @Mustafa-099 Рік тому

    This is Gold content on youtube!!! Loving every bit of it :)

  • @tosindaudu6595
    @tosindaudu6595 2 роки тому +7

    Please do refcell and cell next and soon. Thanks!

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

    If I use mockall, it requires mockall be a part of the dependency in the library. The example here wouldn't work if it's in the dev dependency. (Not sure if it's problem because I thought mock should be dev dependency as it's not related to actual context i.e api call)

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

      Puzzled Rust mocking newb here: I've just asked the same question one year later. Did you get an answer to this? i.e. how to not include that mockall crate when compiling?

  • @GeekMasher
    @GeekMasher 2 роки тому +22

    Great video. One thing I might have added to this is Rusts documentation testing. This is an amazing feature of the language. You will know that the documentation you have written compiles and runs properly as tests in `cargo test`.

    • @codetothemoon
      @codetothemoon  2 роки тому +7

      I actually haven't tried this, will check it out!

  • @andythedishwasher1117
    @andythedishwasher1117 2 роки тому +2

    Super useful breakdown! This will be an essential skill set as I dive into this idea I recently had. I'm trying to determine the potential advantages, if any, of using Rust to indirectly write Javascript via some kind of translation macro for the purposes of maintaining memory safety in the resulting Javascript. This would be different from what frameworks like Yew are doing (which is definitely awesome) by bundling Rust logic into beautiful web assembly that works reluctantly alongside JS like an incompetent older brother who just happened to inherit the keys to the workshop. We wouldn't get the same kind of performance advantages, but we would still benefit from the memory safety checks quite a bit. I discovered that effect when I was very quickly able to rebuild my calculator app from Yew to Vue 3 with minimal debugging and a lot less code.

    • @codetothemoon
      @codetothemoon  2 роки тому +1

      Thanks Andy! That would be an interesting undertaking. I wonder if the brevity of the Vue version is more due to the Vue framework than JavaScript itself. Not sure. Personally when I build with Yew I don't find myself missing JS or React, but I do find myself missing Svelte 🙃

    • @andythedishwasher1117
      @andythedishwasher1117 2 роки тому

      @@codetothemoon Svelte is the big one I plan to explore next before I land on a target framework. Working on a rebuild of my HQ domain with Vue to assess the resulting code base, then probably working on a rebuild and/or additional experiment in Svelte depending on how the Vue build goes. Lots of work ahead!

  • @phenanrithe
    @phenanrithe 2 роки тому +1

    More and more often I find that hosting the unit tests in the same file as the source code is distracting and inconvenient without real benefit, so I'm mostly keeping them in a tests-rs file which is technically the same. Just prepend the file with "#![cfg(test)]". It's easier to switch files than lines in the same file in the test/code design loop, and the tests-rs file is like a detailed spec/example for the whole crate / submodule (or tests_ if it becomes too big). Unless there aren't many tests of course.

    • @phenanrithe
      @phenanrithe 2 роки тому

      (I have to write test-rs instead of test DOT rs otherwise silly UA-cam creates a link, which likely hides the comment as spam...)

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

      I have never seen an example of changing a test file out being an actual use-case. Often because tests should be complete as a whole. Co-location makes it easier to find tests for the corresponding implementations imo.
      The only reason why I'd move unit tests to a separate file would be if the language didn't allow me to have it in the same place.
      If your tests become too big, that is also a good indication that your implementation likely does more than it needs. It makes it easier to discover where separation of concerns could be applied.
      But each to their own, as long as it's consistent I don't see any problem with either way of doing it.

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

      @@dealloc I think it's a question of preference indeed, I just don't like to mix source code with test code. There's just no benefit, only inconvenience unless it's a very small file with very few tests.

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

    Ignore can also be used if the integration test needs some hardware to pass. For example, the library is used to calculate average temperature, and you want to test it with an actual sensor.

  • @davidraymond8994
    @davidraymond8994 2 роки тому +4

    Awesome Rust videos. I am a newbie to Rust and am learning a lot from you thanks so much. Do you have a course offering or anything like that?

    • @codetothemoon
      @codetothemoon  2 роки тому +2

      Thanks David! I don't currently have a course offering (I'd love to have one though!) but I've seen many on Udemy and other online learning sites. Not sure which one is best, maybe ask in r/rust! Also Let's Get Rusty offers a course as well but I haven't tried it.

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

    Really like your Rust videos! Just wanted to say that I prefer to avoid mocking when possible by breaking a function into two parts: 1) a pure function that figures out what needs to be done and returns a description of that activity, and 2) an interpreter for that description. The interpreter will be straightforward and can often just be left for integration testing. All of the gnarly logic will be in the the pure function, which is now far easier to test: given these inputs, you should say that this needs to be done.

  • @irlshrek
    @irlshrek 2 роки тому

    I use --nocapture to print stuff in the tests!

  • @Marcos-tl2vy
    @Marcos-tl2vy Рік тому

    Thanks!!!!🎉🎉❤

  • @SArthur221
    @SArthur221 2 роки тому

    11:12 you said we were going to test the get_answer() function, but what seems to have happened is the exact opposite: that BigComputer's implementation gets called the way we prescribe...

    • @jordanwhittle8713
      @jordanwhittle8713 2 роки тому +1

      I could be wrong but I think the idea behind the ‘times’ part is to specify the way that get_answer USES BigComputer
      Imagine if BigComputer was actually a database and compute_answer created a record in the database. With times(1), you would be able to specify that get_answer only calls compute_answer once to create 1 record in the database instead of calling compute_answer 1000 times and creating 1000 records. You’re not testing the mocked object, just specifying how it must be used.

    • @SArthur221
      @SArthur221 2 роки тому

      @@jordanwhittle8713 good point

  • @noblenetdk
    @noblenetdk 2 роки тому +1

    How about the cargo plugin Nexttest? As alwas great explanation

    • @codetothemoon
      @codetothemoon  2 роки тому

      Never heard of it, I've put it on the list of things to check out!

  • @necauqua
    @necauqua 2 роки тому +5

    There is one more interesting test binary flag, --nocapture.
    And in general, you didn't mention how the stdout is captured and printed only if the test fails - to keep successful test runs tidy

    • @codetothemoon
      @codetothemoon  2 роки тому

      re: nocapture, I actually didn't know about this, thanks for pointing it out. and you're right, I should have mentioned that stdout is for successful tests.

  • @flwi
    @flwi 2 роки тому +1

    Thanks for this helpful video! This will take some time to get used to. I work mostly in scala and just started learning rust. Mixing code and tests in a single file just feels wrong to me so far.

    • @codetothemoon
      @codetothemoon  2 роки тому +1

      Cool - I personally think Scala is a fantastic language - sometimes I wish there was some way to get Rust's performance with a more Scala-like syntax. And yeah, putting tests and code in the same file feels a little weird to me still. But I think there are advantages, like not needing to maintain a test file / directory structure that is essentially a duplicate of the one that already exists for your code.

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

    Another flag worthwhile mentioning IMHO would be cargo test -- --nocapture to occasionally output debug info

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

      good point, probably should have included that!

  • @mareknetzel
    @mareknetzel 2 роки тому +1

    Your videos are perfect!

    • @codetothemoon
      @codetothemoon  2 роки тому

      Thanks Marek, very happy you find them valuable!

  • @masiarek
    @masiarek 2 роки тому +1

    Thank you!

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

    right so for somehting like this we'd definitely use a manual mock.

  • @kentheHar
    @kentheHar 2 роки тому +2

    Do the mocks end up in the normal binary or only the test binary?

    • @codetothemoon
      @codetothemoon  2 роки тому +1

      Good question I'm actually not 100% sure about this, but my hope is that they would be pruned out because they are not used. I'd be surprised if they weren't.

  • @someon3
    @someon3 2 роки тому +1

    What are the real benefits of doing mock than construct the object directly?

    • @codetothemoon
      @codetothemoon  2 роки тому

      Because constructing the object directly would make the test dependent on the behavior of that object - in unit tests we want to test a specific component in isolation, ideally without being affected by that component's dependencies and transitive dependencies. This is a bit easier to see when the component being tested has 10s or 100s of transitive dependencies.

    • @harleyspeedthrust4013
      @harleyspeedthrust4013 2 роки тому

      @@codetothemoon But if the thing being tested can't be isolated in real code because it depends on the thing we're mocking, then how do we expect to be able to test it in isolation? Doesn't that defeat the point of tests - to ensure that our code works when it runs in production?

    • @TehKarmalizer
      @TehKarmalizer 2 роки тому

      @@harleyspeedthrust4013 that’s the argument. That when you test mocks, all you test is your ability to produce mocks. There are other types of testing, though. Unit testing is intended to test low level behavior.

  •  Рік тому

    mockall should be in the devDependencies though right ?

  • @samhanna7382
    @samhanna7382 2 роки тому +1

    is that the default vs code dark theme ?

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

    Hi, I’m qa automation.
    can i use rust for api tests?
    Thx!

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

      I don't think there's anything preventing you from doing this! Whether it's the best choice for such a task, I'm not sure.

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

    what about logging?

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

      specifically in a testing context or in general?

  • @mdshohidurrahman1121
    @mdshohidurrahman1121 2 роки тому +3

    Brain friendly,as always.

    • @codetothemoon
      @codetothemoon  2 роки тому +1

      🧠 nice, brain friendliness is a big priority!

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

    So its impossible to mock structs without changes your code or the dependacy? :(

  • @mufty9006
    @mufty9006 2 роки тому +2

    I always have to change my playback speed back to Normal. Can't afford to miss a word

    • @codetothemoon
      @codetothemoon  2 роки тому

      Nice, I try to avoid any fluff!

    • @theroboman727
      @theroboman727 2 роки тому +1

      There is a browser extension I use called enhancer for youtube, where one of the features is the ability to change playback speed in 0.1x increments (can be configured) using ctrl+scroll wheel. Thought id mention this if you dont have anything similar already.

  • @dr.maninderjitkaur7805
    @dr.maninderjitkaur7805 2 роки тому +2

    I am too sober for tNice tutorials, I'll be back later..

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

    Don’t forget `cargo test - -nocapture`

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

    good luck using mockall for nontrivial cases

  • @awnion
    @awnion 2 роки тому

    That's all great until you actually need something like async_trait + trait inheritance 🤣

  • @vanshkoul1200
    @vanshkoul1200 4 місяці тому +1

    look at me!!

  • @JosephDalrymple
    @JosephDalrymple 2 роки тому

    It's all fun and games until you try and reference a trait in your struct so that you can mock it's _async methods_.
    EDIT emphasized. Should've been more specific in the beginning. 😅

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

      This is the same as get_answer, but `impl` abstracts away what is really going on. The expanded version of using `impl` is this:
      ```
      fn get_answer(computer: &C, question: i32) -> String where C: BigComputer {
      // ...
      }
      ```
      With structs it would be the same format:
      ```
      struct GetAnswerMachine where C: BigComputer {
      computer: C;
      }
      ```
      You can also use the short-hand syntax:
      ```
      struct GetAnswerMachine {
      computer: C;
      }
      ```
      This is called dependency injection, because it allows you to switch out what "C" is, as long as it implements the "BigComputer" trait.
      If GetAnswer does not use the entire BigComputer, you can split up the BigComputer trait into smaller traits and compose them where needed. Smaller traits are easier to deal with and decreases the surface area, avoiding breaking changes that could affect all implementations that rely on large traits.

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

      @@dealloc Unless your struct uses async methods 😅

  • @jeffg4686
    @jeffg4686 2 роки тому

    Mocking Rust is the only way to get through learning it.
    I have full contempt by now, believe me.
    Question regarding asserts outside of tests - just in main code - would you always use debug_assert there. I'm still trying to figure out best practices in rust. My thought is that assert! in regular code is likely a bad idea (since it panics instead of allowing you a change to deal with an error result), but that debug_assert!() is useful since it's only in debug mode, but then that's kinda cheating your way out of having real error handling. So maybe even debug_assert isn't that useful in regular code. Maybe just as a placeholder to say "write some error handling here later"

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

      Panicing is perfectly fine, mostly for internal implementations rather than user-facing APIs.
      It's better to panic and crash a program than ending up in a bad state that is unrecoverable and hard to debug. Assertions are not a replacement of error handling, but rather a compliment to it, by restricting the surface area of an implementation to avoid leaking internal details throughout the entire library/application, especially to user-facing APIs. Panics should be treated as bugs in the implementation. Rather than being a way to validate user input, it should be a way to validate input passed by the internal system.
      While you could pass Results everywhere in place of assertions but it makes it harder to avoid breaking changes in the code base and often easily leaks into user-land, where there is no way for the user to recover.
      Note that "user" in this case can be a user of a library, or a service interfacing with other services or programs.

  • @fleeb
    @fleeb 2 роки тому +1

    Ooooh... by 'Mocking Rust', you mean using mocks to imitate real objects. I thought this video was going to make fun of the language.

    • @codetothemoon
      @codetothemoon  2 роки тому +1

      yeah, it was a somewhat lame play on words 🙃

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

    Why everything becomes dead complicated when it comes to Rust?
    Boy, it's just testing. Do you really think this is 'simple' ?

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

      I don't think everything is complicated when it comes to Rust. But I think what you need to do to get mocking working is pretty awful. My gut feeling is that this complexity is due to the lack of support for inheritance, but I'm not 100% sure. In any case I hope the necessary language features get implemented that allow mocking to be a bit more concise.