43

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?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Freewind
  • 193,756
  • 157
  • 432
  • 708

4 Answers4

41

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.

Dan Field
  • 20,885
  • 5
  • 55
  • 71
  • 9
    Sorry, but I disagree with the statement "something worth testing is worth being public". By that logic, no fetching, mapping, processing etc. method is worth testing, because in most cases there's no reason to make them public. But that would mean, I'd have to run through say 90% of "public" code in order to test one nested private method, who's result I probably can't even verify directly, because it's discarded on the way. It seems way more efficient to just test that private method directly than trying to figure out whether it works correctly by evaluating its indirect effects. – Stacky Mar 24 '21 at 14:12
  • IME, such functions are often worth encapsulating in their own classes, so they can be tested or appropriately mocked in other tests, in isolation. – Dan Field Mar 30 '21 at 23:18
  • Yes, it does, e.g. `class Foo { Foo({@visibleForTesting this.someInt = 42}; final int someInt; }` – Dan Field Aug 16 '21 at 22:42
  • "Usually if it's something worth testing, it's something that's worth being public" Your words made me rethink my code design and yeah, I think my current code design solves two problems at once which is not good (breaking SRP). Thank you! – Hzzkygcs Mar 16 '23 at 14:51
20

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:

  • Create a kind of helper class with this hello method as public. You can then easily unit test it
  • Let your current class use an instance of this helper class
  • Test the public methods of your current class which relies on _hello: if this private has a bug, it should be catch by those higher level tests
gturri
  • 13,807
  • 9
  • 40
  • 57
  • Your advise makes sense, but it looks like for all languages. Is it best solution for Dart too? – Freewind Feb 09 '14 at 10:01
  • As you guessed, my answer isn't Dart-specific. – gturri Feb 09 '14 at 10:03
  • 3
    I'm compelled by strict language's encapsulation to follow this rule (as most of us), and I don't believe this is enough of a reason since private methods can get messy, complex, recursive, etc. So to the OP, I suggest you really do extract those kind of stuff into a new class, and keep privates for repeated boilerplate (until you run into exceptions to this rule, of course). This would be a great opportunity to make your code generic, abstract and satisfy all those best practices we all love while writing quality code. – MasterMastic Feb 09 '14 at 10:47
  • 3
    I hate this school, I still need to test private methods – amd Dec 13 '18 at 16:39
  • 1
    Yeah I don't test any of my back-end code for the same reason. It's all just an implementation detail and the public UI is what matters. If I wanted to test it I would just move it into the front-end code. This way I can also change my back-end implementation without updating any tests. Nobody ever ends up writing anything too complicated that it should be tested in any of the private methods... I mean back-end code. – Gerry Aug 02 '21 at 16:33
9

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.

user5354671
  • 311
  • 4
  • 4
3

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.

Max Tromp
  • 690
  • 7
  • 13
  • 3
    The approach you suggest is correct. However **that the code in `src` is private to the package** (in Dart the word library is used to refer to individual .dart files - more precisely those without a part of directive) **is a convention**, not a constraint. Perhaps it seems to you a constraint because the IDEs (like IntelliJ or Android Studio) respect it, so they do not propose to import these files, but it is possible to write the import manually and you will be able to compile and execute the code. – Mabsten Mar 26 '21 at 21:20