Love the nonchalant making of a function representing a "long-running calculation that results in the answer 42", with no further explanation than that. ;-)
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`.
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?
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.
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)
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?
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
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.
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.
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.
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 🙃
@@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!
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.
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.
@@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.
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.
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.
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.
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...
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.
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.
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.
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.
@@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?
@@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.
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. 😅
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.
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"
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.
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.
Love the nonchalant making of a function representing a "long-running calculation that results in the answer 42", with no further explanation than that. ;-)
As a newbie to Rust and a strong supporter of TDD this is probably the best byte-sized intro to testing on TH-cam today imo. Thanks a lot!
Thanks calvinlucian, glad you found it valuable!
This guy is awesome
Perfect timing. I've been struggling with mocks in Rust. Thanks!
Glad this video could help! Mocking in Rust is definitely a bit tricky.
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`.
I actually haven't tried this, will check it out!
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?
You're awesome! I've been procrastinating reading the Rust testing docs & you taught me everything I need to get started :)
Fantastic, really happy it was helpful!
Please do refcell and cell next and soon. Thanks!
I've put it on the video idea list!
This is Gold content on youtube!!! Loving every bit of it :)
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.
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)
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?
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
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.
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?
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.
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.
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 🙃
@@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!
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.
(I have to write test-rs instead of test DOT rs otherwise silly TH-cam creates a link, which likely hides the comment as spam...)
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.
@@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.
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.
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.
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.
Another flag worthwhile mentioning IMHO would be cargo test -- --nocapture to occasionally output debug info
good point, probably should have included that!
right so for somehting like this we'd definitely use a manual mock.
Your videos are perfect!
Thanks Marek, very happy you find them valuable!
How about the cargo plugin Nexttest? As alwas great explanation
Never heard of it, I've put it on the list of things to check out!
Brain friendly,as always.
🧠 nice, brain friendliness is a big priority!
I use --nocapture to print stuff in the tests!
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...
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.
@@jordanwhittle8713 good point
mockall should be in the devDependencies though right ?
I always have to change my playback speed back to Normal. Can't afford to miss a word
Nice, I try to avoid any fluff!
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.
Thank you!
You're welcome thanks for watching!
Hi, I’m qa automation.
can i use rust for api tests?
Thx!
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.
Thanks!!!!🎉🎉❤
Do the mocks end up in the normal binary or only the test binary?
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.
What are the real benefits of doing mock than construct the object directly?
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.
@@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?
@@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.
So its impossible to mock structs without changes your code or the dependacy? :(
is that the default vs code dark theme ?
Yep I believe it's called Dark+!
what about logging?
specifically in a testing context or in general?
Don’t forget `cargo test - -nocapture`
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. 😅
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.
@@dealloc Unless your struct uses async methods 😅
good luck using mockall for nontrivial cases
That's all great until you actually need something like async_trait + trait inheritance 🤣
I am too sober for tNice tutorials, I'll be back later..
🔥
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"
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.
look at me!!
👁️
Ooooh... by 'Mocking Rust', you mean using mocks to imitate real objects. I thought this video was going to make fun of the language.
yeah, it was a somewhat lame play on words 🙃
Why everything becomes dead complicated when it comes to Rust?
Boy, it's just testing. Do you really think this is 'simple' ?
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.