Reputation: 198198
Say I defined a private function in a dart file hello.dart
:
_hello() {
return "world";
}
I want to test it in another file mytest.dart
:
library mytest;
import 'dart:unittest/unittest.dart';
main() {
test('test private functions', () {
expect(_hello(), equals("world"));
}
}
But unfortunately, the test code can't be compiled. But I do need to test that private _hello
function. Is there any solution?
Upvotes: 46
Views: 16916
Reputation: 371
I don't like either of the above answers. dart's private variable test design is very bad. dart's private visibility is based on library, and each .dart file is a library by default, similar language is rust, but rust can write test code directly in the file, there is no private visibility problem, while dart does not allow this.
Again, I don't think @visibleForTesting is a valid solution, Because @visibleForTesting can only be used to decorate public declarations, it serves as a mere analysis reminder that developers cannot invoke these declarations in other files, But from a syntax point of view, developers can't use the _ prefix either, so the form, public, private, becomes confusing. and violates dart's own naming rules.
The argument that one should not test private, or that they should be separated into other classes, is like a justification that is completely unacceptable.
First, private exist because they belong to a business logic/model etc. in a contextual relationship, and it does not make logical sense to separate it into another class.
Second, if you must do this, it will greatly increase the complexity of the code, for example, you move to other classes will lose access to the context variables, or you have to pass a separate reference, or have to create an instance of the class, indeed, then you can finally do some mocks, but you also add a layer of abstraction, It's hard to imagine that if you were to do this for the whole project, you'd probably double your entire code layers.
For now, If you want your dart package to get more than 90% coverage, you should not define any private.
It sounds harsh, but that's the real story.
[Alternative] No one seems to have mentioned this yet,
Using part / part of to expose the privates, you can define a test-specific .dart file as the public interface to the library(file) to be tested, and use it to expose all the private declarations that need to be tested. you can name them xxx.fortest.dart
But this is more of a psychological solution, since you are still essentially exposing all private variables/methods
But at least, it's better than splitting class, Also, if one day dart finally solves this problem, we can simply delete these .fortest.dart files.
Upvotes: 12
Reputation: 21641
While I agree that private methods/classes shouldn't be part of your tests, the meta package does provide an @visibleForTesting
attribute, and the analyzer will give you a warning if you attempt to use the member outside of its original library or a test. You can use it like this:
import 'package:meta/meta.dart';
@visibleForTesting
String hello() {
return "world";
}
Your tests will now be able to use it without error or warning, but if someone else tries to use it they'll get a warning.
Again, as to the wisdom of doing this is another question - usually if it's something worth testing, it's something that's worth being public (or it'll get tested through your public interfaces and that's what really matters anyway). At the same time, you might just want to have rigorous tests or test driven principles even for your private methods/classes so - Dart lets you this way.
Edit to add: If you're developing a library and your file with @visibleForTesting
will be exported, you are essentially adding public API. Someone can consume that with the analyzer turned off (or just ignore the warning), and if you remove it later you may break them.
Upvotes: 45
Reputation: 780
A suggestion would be to NOT make methods/classes private but to move code, where you want to hide implementation details, to the lib/src folder.
This folder is considered private.
I found this approach on the fuchsia.dev page in this section under "Testing".
If you want to expose those private methods/classes, that are located in the src folder, to the public, you could export them inside your lib/main file.
I tried to import one of my libraries A (projects are libraries) into another library B and couldn't import code that was in the src folder of library A. According to this StackOverflow answer it could still be possible to access the src folder from A in library B.
From the dart documentation
As you might expect, the library code lives under the lib directory and is public to other packages. You can create any hierarchy under lib, as needed. By convention, implementation code is placed under lib/src. Code under lib/src is considered private; other packages should never need to import src/.... To make APIs under lib/src public, you can export lib/src files from a file that’s directly under lib.
Upvotes: 3
Reputation: 14599
Several people believe we shouldn't test private directly: it should be tested through the public interface.
An advantage of following this guidance, is that your test won't depend on your implementation. Said differently: if you want to change your private without changing what you expose to the world, then you won't have to touch your tests.
According to this school of though, if your private is important enough to justify a unit test, then it might make sense to extract it in a new class.
Putting all this together, what you could do here, is:
hello
method as public. You can then easily unit test it_hello
: if this private has a bug, it should be catch by those higher level testsUpvotes: 21