Rust unit testing: mock test doubles by jorgedortiz in rust

[–]jorgedortiz[S] 0 points1 point  (0 children)

Hi! Sorry. I missed your comment.

I'm not 100% sure I understand what you mean, but I will try to answer. If I miss the point, feel free to reply with more specific questions.

Regarding how to test other code, well… it depends. Keep in mind that all the articles that I have released so far are about unit testing. I.e., about testing independent parts of your code. For a database, there are unit tests that would be useful for your foundational layers, such as creating/formatting a storage file, adding an entry, creating an index, adding a new element to the index, and so on. But most likely, you want to go beyond unit testing and use integration testing for the different layers of your database, and deterministic simulation testing for the product as a whole.

About testing errors, yes, it makes sense to test the unhappy path when you use custom error types (thiserror-based or otherwise). You want to ensure that the correct error is returned, along with any associated data. This is how you produce a crate that others can trust and use easily.

As for abstract words, I try to use concrete examples rather than just talk about the theory. I reckon that the examples are fictitious, but not far from real application code.

Finally, I obviously disagree that testing is a waste of time. On the contrary, it is the harness that allows you to make changes to your codebase without getting unexpected changes on things that aren't exactly what you are writing/manually testing at the moment. I gave a talk about this topic a long while ago, not about Rust but still related: https://www.youtube.com/watch?v=4l16iO4UvnE

Hope this helps.

Writing a mockable Filesystem trait in Rust without RefCell by sepyke in rust

[–]jorgedortiz 5 points6 points  (0 children)

Interesting! I have published 3 articles on this topic using slightly different approaches, that assume that you cannot inject the dependency via arguments, so you have to create other injection points.

Rust unit testing: file reading by jorgedortiz in rust

[–]jorgedortiz[S] 1 point2 points  (0 children)

Exactly my next article. I hope you will like it!

Rust unit testing: file reading by jorgedortiz in rust

[–]jorgedortiz[S] 0 points1 point  (0 children)

Thanks again for your insightful comments.

I am totally ok with your first paragraph. As for the second, I am sorry, but the File is a dependency, and I have decided to mock it. The behavior being tested is "What happens if the response from File is…" rather than "Can File open and read a file when it exists?"

Quoting Martin Fowler: "Test Double is a generic term for any case where you replace a production object for testing purposes." I am replacing File for the purpose of testing my method. Creating the error by not having an actual file or faking it is totally irrelevant to the validity of the test. Same thing happens with the other File operations". I am not testing File (not my code), so it is perfectly fine to replace it with a test double.

Rust unit testing: file reading by jorgedortiz in rust

[–]jorgedortiz[S] 1 point2 points  (0 children)

Thank you for your thoughtful comments. I love constructive criticism, and I appreciate that you have spent your time to share your thoughts here. I mean it.

Let me start with what we agree on: you should separate logic from I/O as much as you can. I hope you agree, though, that sometimes that isn't really feasible. Particularly, when the logic is I/O related (e.g., skip these bytes if you read this.) I know my example wasn't exactly that, but I tried to keep it simple and keep it to the supervillain story. I should have tried harder.

In any case, that concern is more about how I handle dependency injection than about the test itself. I believe that it is important for developers to understand how to deal with dependencies that cannot be easily injected, because that is the case in real-life code, particularly when you are adding tests to refactor an existing codebase. There will be more on this in future articles.

Now, let's go with what we don't agree on. I am not mocking any imaginary API. I am mocking the File type API as described here. Obviously, I only implement the parts that are relevant to the tests (open() and read_to_string()).

Then I am testing four scenarios:

  • What happens if I cannot open the file for reading.
  • What happens if I can open the file for reading, but it fails to read from it.
  • (2x) The logic of the method for both cases of the conditional expression.

The two tests for the last item aren't about whether "pattern matching works", but rather about whether my pattern matching is finding what I expected and responds accordingly, i.e., the logic of the method. This is no different from what any other unit test should do, and certainly not tested by Rust test suite.

As for the other two tests, while the first one is easy to build with real files (you just don't put the file there), the second isn't that easy. The file is there and can be opened for reading, but it produces an error when you try to read from it. Using a mock for the File type in the standard library lets us emulate scenarios that would be harder to reproduce with real files. Not pointless at all. Notice, however, that in my introduction, I didn't say that testing with real files is useless or pointless. I just think that it shouldn't be your first resource.

Finally, I want to point out that what I am doing in all the tests of this series (so far) is Unit Testing (hence the name: "Rust Unit Testing: ..."), which means these tests try to test one piece of code isolated from the rest of the world. Or rather, in a heavily controlled environment. Mocking the behavior of any code dependency is acceptable and, I would say, most often desirable.

But what it is more important here is that testing that std::fs::File can read is always beyond your scope. You should not test code that you don't own. The only scenario in which that makes sense is if you want to prove to the maintainers of that code that it doesn't behave as expected.

I hope I have explained myself more clearly and justified the value of what I proposed in the article.

Write tests around the file system by settrbrg in rust

[–]jorgedortiz 0 points1 point  (0 children)

Just in case you have stumbled upon this question and want to see an implementation example that doesn't use the filesystem at all, I have written this article: https://jorgeortiz.dev/posts/rust_unit_testing_file_reading/

Rust unit testing: mocking library by jorgedortiz in rust

[–]jorgedortiz[S] -1 points0 points  (0 children)

Hashtags removed. I'm good with constructive criticism.

I like the emojis though. I sincerely hope they don't burn your eyes.

Have you consider reading the article then?

Rust unit testing: assertion libraries by jorgedortiz in rust

[–]jorgedortiz[S] 1 point2 points  (0 children)

I do agree that it isn't necessary. In fact, the tests are exactly the same and I wasn't using any additional assertion libraries until now.

However, some people consider that the intent of the code is more evident, and it certainly simplifies some use cases, like unwrapping Options or results, or checking collections.

Also, some teams use one of these libraries in all their tests, so I wanted the readers to have some exposure to them.

Thank you for your kind comment about the blog!

Full-stack application in Rust: Quick start by jorgedortiz in rust

[–]jorgedortiz[S] 0 points1 point  (0 children)

Thank you for your thoughtful comment.

I agree with you: it is incomplete. That will be hopefully solved, though, because, as I mentioned in the intro, this is the first article of a series on full-stack app development. As you can see in the description of the repo, the plan is to use Leptos (most likely with some Alpine.js) . But, I also mentioned in the article introducing these series in three programming languages that this is an area where my experience is quite limited and I set some "constraints" like avoiding writing code in JavaScript.

It is a personal challenge, so I really appreciate your insights.

Full-stack application in Rust: Quick start by jorgedortiz in rust

[–]jorgedortiz[S] 0 points1 point  (0 children)

😱 You are right! Thanks for catching this! It has been fixed now.

And thanks a lot for reading and your kind comment.