Repository: mobxjs/mobx.dart

Issue:
Let generated code also inherit meta protected
I was working with mobx and figured out that `@protected` meta is not taken into account during the generate step.
Because of that there is no proper analysis to be performed by dart analysis. It does not look deeper for `@protected` when the overriding class is exposing the method as public.

If the generator would also annotate the `@protected` function with `@protected`, calling the function from outside the class will nicely expose an analysis error.

Here is an example implementation:

```dart
import 'package:meta/meta.dart';
import 'package:mobx/mobx.dart';

part 'protected_bug_base.g.dart';

class Foo = _Foo with _$Foo;

abstract class _Foo with Store {
  @protected
  void doStuff() {
    print('stuff done');
  }

  @action
  @protected
  void bar() {
    print('bar done');
  }
}
```

```dart
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'protected_bug_base.dart';

// **************************************************************************
// StoreGenerator
// **************************************************************************

// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers

mixin _$Foo on _Foo, Store {
  late final _$_FooActionController =
      ActionController(name: '_Foo', context: context);

  @override
  void bar() {
    final _$actionInfo = _$_FooActionController.startAction(name: '_Foo.bar');
    try {
      return super.bar();
    } finally {
      _$_FooActionController.endAction(_$actionInfo);
    }
  }

  @override
  String toString() {
    return '''

    ''';
  }
}
```

Because `void bar()` is missing `@protected` in the generated file, calling the function from outside the class does not show an analysis warning.

Would it be possible to look for protected (and perhaps others meta) and expose them in the generated code?

```yaml
name: protected_bug
description: A starting point for Dart libraries or applications.
version: 1.0.0

environment:
  sdk: 2.17.6

dependencies:
  meta: 1.7.0
  mobx: 2.1.3

dev_dependencies:
  lints: 2.0.0
  test: 1.21.4
  build_runner: 2.3.0
  mobx_codegen: 2.1.1
```


Discussion: Hi @praxamarnix ,
Yes, that's a good suggestion. As a workaround, you can do the following ( although not nice )
```dart
class Foo extends _Foo with _$Foo {

  @override
  @protected
  void bar() {
    super.bar();
  }

}
```

Fix patch:
diff --git a/mobx/analysis_options.yaml b/mobx/analysis_options.yaml
index 7932dfe2e..5b974cb92 100644
--- a/mobx/analysis_options.yaml
+++ b/mobx/analysis_options.yaml
@@ -4,5 +4,5 @@ analyzer:
   exclude:
     - test/*.mocks.dart
     - example/lib/*.dart
-  strong-mode:
-    implicit-casts: false
+  language:
+    strict-casts: true
diff --git a/mobx/lib/version.dart b/mobx/lib/version.dart
index 4a91b916d..e39cab4f5 100644
--- a/mobx/lib/version.dart
+++ b/mobx/lib/version.dart
@@ -1,5 +1,4 @@
 // Generated via set_version.dart. !!!DO NOT MODIFY BY HAND!!!
 
 /// The current version as per `pubspec.yaml`.
-
-const version = '2.1.4';
\ No newline at end of file
+const version = '2.1.4';
diff --git a/mobx_codegen/CHANGELOG.md b/mobx_codegen/CHANGELOG.md
index 52aa13ec9..a1bac5b17 100644
--- a/mobx_codegen/CHANGELOG.md
+++ b/mobx_codegen/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+- Adds support for annotations `@protected`, `@visibleForTesting` and `@visibleForOverriding` for actions, observables futures and observables stream.
+
 ## 2.1.0 - 2.1.1
 
 - Update analyzer version to `>=4.4.0 <6.0.0`
diff --git a/mobx_codegen/lib/src/store_class_visitor.dart b/mobx_codegen/lib/src/store_class_visitor.dart
index bc613469b..80015b460 100644
--- a/mobx_codegen/lib/src/store_class_visitor.dart
+++ b/mobx_codegen/lib/src/store_class_visitor.dart
@@ -168,17 +168,23 @@ class StoreClassVisitor extends SimpleElementVisitor {
 
       if (element.isAsynchronous) {
         final template = AsyncActionTemplate(
-            storeTemplate: _storeTemplate,
-            isObservable: _observableChecker.hasAnnotationOfExact(element),
-            method:
-                MethodOverrideTemplate.fromElement(element, typeNameFinder));
+          storeTemplate: _storeTemplate,
+          isObservable: _observableChecker.hasAnnotationOfExact(element),
+          method: MethodOverrideTemplate.fromElement(element, typeNameFinder),
+          hasProtected: element.hasProtected,
+          hasVisibleForOverriding: element.hasVisibleForOverriding,
+          hasVisibleForTesting: element.hasVisibleForTesting,
+        );
 
         _storeTemplate.asyncActions.add(template);
       } else {
         final template = ActionTemplate(
-            storeTemplate: _storeTemplate,
-            method:
-                MethodOverrideTemplate.fromElement(element, typeNameFinder));
+          storeTemplate: _storeTemplate,
+          method: MethodOverrideTemplate.fromElement(element, typeNameFinder),
+          hasProtected: element.hasProtected,
+          hasVisibleForOverriding: element.hasVisibleForOverriding,
+          hasVisibleForTesting: element.hasVisibleForTesting,
+        );
 
         _storeTemplate.actions.add(template);
       }
@@ -189,14 +195,20 @@ class StoreClassVisitor extends SimpleElementVisitor {
 
       if (_asyncChecker.returnsFuture(element)) {
         final template = ObservableFutureTemplate(
-            method:
-                MethodOverrideTemplate.fromElement(element, typeNameFinder));
+          method: MethodOverrideTemplate.fromElement(element, typeNameFinder),
+          hasProtected: element.hasProtected,
+          hasVisibleForOverriding: element.hasVisibleForOverriding,
+          hasVisibleForTesting: element.hasVisibleForTesting,
+        );
 
         _storeTemplate.observableFutures.add(template);
       } else if (_asyncChecker.returnsStream(element)) {
         final template = ObservableStreamTemplate(
-            method:
-                MethodOverrideTemplate.fromElement(element, typeNameFinder));
+          method: MethodOverrideTemplate.fromElement(element, typeNameFinder),
+          hasProtected: element.hasProtected,
+          hasVisibleForOverriding: element.hasVisibleForOverriding,
+          hasVisibleForTesting: element.hasVisibleForTesting,
+        );
 
         _storeTemplate.observableStreams.add(template);
       }
diff --git a/mobx_codegen/lib/src/template/action.dart b/mobx_codegen/lib/src/template/action.dart
index 72aa0fa25..52831facf 100644
--- a/mobx_codegen/lib/src/template/action.dart
+++ b/mobx_codegen/lib/src/template/action.dart
@@ -1,16 +1,26 @@
+import 'package:mobx_codegen/src/template/annotations_generator_mixin.dart';
 import 'package:mobx_codegen/src/template/method_override.dart';
 import 'package:mobx_codegen/src/template/store.dart';
 
-class ActionTemplate {
-  ActionTemplate({required this.storeTemplate, required this.method});
+class ActionTemplate with AnnotationsGenerator {
+  ActionTemplate({
+    required this.storeTemplate,
+    required this.method,
+    required bool hasProtected,
+    required bool hasVisibleForOverriding,
+    required bool hasVisibleForTesting,
+  }) {
+    this.hasProtected = hasProtected;
+    this.hasVisibleForOverriding = hasVisibleForOverriding;
+    this.hasVisibleForTesting = hasVisibleForTesting;
+  }
 
   final StoreTemplate storeTemplate;
   final MethodOverrideTemplate method;
 
   @override
-  // ignore: prefer_single_quotes
   String toString() => """
-    @override
+    $annotations
     ${method.returnType} ${method.name}${method.typeParams}(${method.params}) {
       final _\$actionInfo = ${storeTemplate.actionControllerName}.startAction(name: '${storeTemplate.parentTypeName}.${method.name}${method.typeParams}');
       try {
diff --git a/mobx_codegen/lib/src/template/annotations_generator_mixin.dart b/mobx_codegen/lib/src/template/annotations_generator_mixin.dart
new file mode 100644
index 000000000..11a24bc81
--- /dev/null
+++ b/mobx_codegen/lib/src/template/annotations_generator_mixin.dart
@@ -0,0 +1,23 @@
+mixin AnnotationsGenerator {
+  bool hasProtected = false;
+  bool hasVisibleForOverriding = false;
+  bool hasVisibleForTesting = false;
+
+  String get annotations {
+    final List<String> annotations = ['@override'];
+
+    if (hasProtected) {
+      annotations.add('@protected');
+    }
+
+    if (hasVisibleForOverriding) {
+      annotations.add('@visibleForOverriding');
+    }
+
+    if (hasVisibleForTesting) {
+      annotations.add('@visibleForTesting');
+    }
+
+    return annotations.join('\n');
+  }
+}
diff --git a/mobx_codegen/lib/src/template/async_action.dart b/mobx_codegen/lib/src/template/async_action.dart
index a746dc60c..34de31108 100644
--- a/mobx_codegen/lib/src/template/async_action.dart
+++ b/mobx_codegen/lib/src/template/async_action.dart
@@ -1,11 +1,20 @@
+import 'package:mobx_codegen/src/template/annotations_generator_mixin.dart';
 import 'package:mobx_codegen/src/template/method_override.dart';
 import 'package:mobx_codegen/src/template/store.dart';
 
-class AsyncActionTemplate {
-  AsyncActionTemplate(
-      {required this.storeTemplate,
-      required this.isObservable,
-      required this.method});
+class AsyncActionTemplate with AnnotationsGenerator {
+  AsyncActionTemplate({
+    required this.storeTemplate,
+    required this.isObservable,
+    required this.method,
+    required bool hasProtected,
+    required bool hasVisibleForOverriding,
+    required bool hasVisibleForTesting,
+  }) {
+    this.hasProtected = hasProtected;
+    this.hasVisibleForOverriding = hasVisibleForOverriding;
+    this.hasVisibleForTesting = hasVisibleForTesting;
+  }
 
   final StoreTemplate storeTemplate;
   final bool isObservable;
@@ -27,7 +36,7 @@ class AsyncActionTemplate {
   String toString() => """
   late final $_actionField = AsyncAction('${storeTemplate.parentTypeName}.${method.name}', context: context);
 
-  @override
+  $annotations
   $_futureType${method.returnTypeArgs} ${method.name}${method.typeParams}(${method.params}) {
     return $_wrappedMethodCall;
   }""";
diff --git a/mobx_codegen/lib/src/template/observable_future.dart b/mobx_codegen/lib/src/template/observable_future.dart
index 902917164..9adc665ab 100644
--- a/mobx_codegen/lib/src/template/observable_future.dart
+++ b/mobx_codegen/lib/src/template/observable_future.dart
@@ -1,14 +1,23 @@
+import 'package:mobx_codegen/src/template/annotations_generator_mixin.dart';
 import 'package:mobx_codegen/src/template/method_override.dart';
 
-class ObservableFutureTemplate {
-  ObservableFutureTemplate({required this.method});
+class ObservableFutureTemplate with AnnotationsGenerator {
+  ObservableFutureTemplate({
+    required this.method,
+    required bool hasProtected,
+    required bool hasVisibleForOverriding,
+    required bool hasVisibleForTesting,
+  }) {
+    this.hasProtected = hasProtected;
+    this.hasVisibleForOverriding = hasVisibleForOverriding;
+    this.hasVisibleForTesting = hasVisibleForTesting;
+  }
 
   final MethodOverrideTemplate method;
 
   @override
-  // ignore: prefer_single_quotes
   String toString() => """
-  @override
+  $annotations
   ObservableFuture${method.returnTypeArgs} ${method.name}${method.typeParams}(${method.params}) {
     final _\$future = super.${method.name}${method.typeArgs}(${method.args});
     return ObservableFuture${method.returnTypeArgs}(_\$future, context: context);
diff --git a/mobx_codegen/lib/src/template/observable_stream.dart b/mobx_codegen/lib/src/template/observable_stream.dart
index b9ed5cd36..4d8e28914 100644
--- a/mobx_codegen/lib/src/template/observable_stream.dart
+++ b/mobx_codegen/lib/src/template/observable_stream.dart
@@ -1,14 +1,23 @@
+import 'package:mobx_codegen/src/template/annotations_generator_mixin.dart';
 import 'package:mobx_codegen/src/template/method_override.dart';
 
-class ObservableStreamTemplate {
-  ObservableStreamTemplate({required this.method});
+class ObservableStreamTemplate with AnnotationsGenerator {
+  ObservableStreamTemplate({
+    required this.method,
+    required bool hasProtected,
+    required bool hasVisibleForOverriding,
+    required bool hasVisibleForTesting,
+  }) {
+    this.hasProtected = hasProtected;
+    this.hasVisibleForOverriding = hasVisibleForOverriding;
+    this.hasVisibleForTesting = hasVisibleForTesting;
+  }
 
   final MethodOverrideTemplate method;
 
   @override
-  // ignore: prefer_single_quotes
   String toString() => """
-  @override
+  $annotations
   ObservableStream${method.returnTypeArgs} ${method.name}${method.typeParams}(${method.params}) {
     final _\$stream = super.${method.name}${method.typeArgs}(${method.args});
     return ObservableStream${method.returnTypeArgs}(_\$stream, context: context);
diff --git a/mobx_codegen/lib/version.dart b/mobx_codegen/lib/version.dart
index 416e1234e..855f13a8b 100644
--- a/mobx_codegen/lib/version.dart
+++ b/mobx_codegen/lib/version.dart
@@ -1,4 +1,4 @@
 // Generated via set_version.dart. !!!DO NOT MODIFY BY HAND!!!
 
 /// The current version as per `pubspec.yaml`.
-const version = '2.1.1';
+const version = '2.2.0';
diff --git a/mobx_codegen/pubspec.yaml b/mobx_codegen/pubspec.yaml
index 4395840fb..3f7af9ee6 100644
--- a/mobx_codegen/pubspec.yaml
+++ b/mobx_codegen/pubspec.yaml
@@ -1,6 +1,6 @@
 name: mobx_codegen
 description: Code generator for MobX that adds support for annotating your code with @observable, @computed, @action and also creating Store classes.
-version: 2.1.1
+version: 2.2.0
 
 homepage: https://github.com/mobxjs/mobx.dart
 issue_tracker: https://github.com/mobxjs/mobx.dart/issues
diff --git a/mobx_examples/lib/form/form_store.dart b/mobx_examples/lib/form/form_store.dart
index c065289f6..05a4d6827 100644
--- a/mobx_examples/lib/form/form_store.dart
+++ b/mobx_examples/lib/form/form_store.dart
@@ -1,8 +1,6 @@
-import 'dart:ui';
-
+import 'package:flutter/widgets.dart';
 import 'package:mobx/mobx.dart';
-// ignore: import_of_legacy_library_into_null_safe
-import 'package:validators2/validators.dart';
+import 'package:validators2/validators2.dart';
 
 part 'form_store.g.dart';
 
@@ -48,7 +46,6 @@ abstract class _FormStore with Store {
   }
 
   @action
-  // ignore: avoid_void_async
   Future validateUsername(String value) async {
     if (isNull(value) || value.isEmpty) {
       error.username = 'Cannot be blank';


Test patch:
diff --git a/mobx_codegen/test/data/annotations_test_class_input.dart b/mobx_codegen/test/data/annotations_test_class_input.dart
new file mode 100644
index 000000000..e3be86903
--- /dev/null
+++ b/mobx_codegen/test/data/annotations_test_class_input.dart
@@ -0,0 +1,49 @@
+library generator_sample;
+
+import 'package:meta/meta.dart';
+import 'package:mobx/mobx.dart';
+
+part 'generator_sample.g.dart';
+
+class AnnotationsTestClass = AnnotationsTestClassBase
+    with _$AnnotationsTestClass;
+
+abstract class AnnotationsTestClassBase with Store {
+  AnnotationsTestClassBase(this.foo);
+
+  @observable
+  String foo = '';
+
+  @action
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  void actionAnnotated() {
+    foo = 'Action annotated';
+  }
+
+  @action
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  Future<void> asyncActionAnnotated() async {
+    foo = 'AsyncAction annotated';
+  }
+
+  @action
+  @observable
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  Future<void> observableFutureAnnotated() async {
+    foo = 'ObservableFuture annotated';
+  }
+
+  @observable
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  Stream<String> observableStreamAnnotated() async* {
+    yield 'ObservableFuture annotated';
+  }
+}
diff --git a/mobx_codegen/test/data/annotations_test_class_output.dart b/mobx_codegen/test/data/annotations_test_class_output.dart
new file mode 100644
index 000000000..455608dd2
--- /dev/null
+++ b/mobx_codegen/test/data/annotations_test_class_output.dart
@@ -0,0 +1,76 @@
+mixin _$AnnotationsTestClass on AnnotationsTestClassBase, Store {
+  late final _$fooAtom =
+      Atom(name: 'AnnotationsTestClassBase.foo', context: context);
+
+  @override
+  String get foo {
+    _$fooAtom.reportRead();
+    return super.foo;
+  }
+
+  @override
+  set foo(String value) {
+    _$fooAtom.reportWrite(value, super.foo, () {
+      super.foo = value;
+    });
+  }
+
+  @override
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  ObservableStream<String> observableStreamAnnotated() {
+    final _$stream = super.observableStreamAnnotated();
+    return ObservableStream<String>(_$stream, context: context);
+  }
+
+  late final _$asyncActionAnnotatedAsyncAction = AsyncAction(
+      'AnnotationsTestClassBase.asyncActionAnnotated',
+      context: context);
+
+  @override
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  Future<void> asyncActionAnnotated() {
+    return _$asyncActionAnnotatedAsyncAction
+        .run(() => super.asyncActionAnnotated());
+  }
+
+  late final _$observableFutureAnnotatedAsyncAction = AsyncAction(
+      'AnnotationsTestClassBase.observableFutureAnnotated',
+      context: context);
+
+  @override
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  ObservableFuture<void> observableFutureAnnotated() {
+    return ObservableFuture<void>(_$observableFutureAnnotatedAsyncAction
+        .run(() => super.observableFutureAnnotated()));
+  }
+
+  late final _$AnnotationsTestClassBaseActionController =
+      ActionController(name: 'AnnotationsTestClassBase', context: context);
+
+  @override
+  @protected
+  @visibleForOverriding
+  @visibleForTesting
+  void actionAnnotated() {
+    final _$actionInfo = _$AnnotationsTestClassBaseActionController.startAction(
+        name: 'AnnotationsTestClassBase.actionAnnotated');
+    try {
+      return super.actionAnnotated();
+    } finally {
+      _$AnnotationsTestClassBaseActionController.endAction(_$actionInfo);
+    }
+  }
+
+  @override
+  String toString() {
+    return '''
+foo: ${foo}
+    ''';
+  }
+}
diff --git a/mobx_codegen/test/generator_usage_test.g.dart b/mobx_codegen/test/generator_usage_test.g.dart
index 389379e30..f8a2b5516 100644
--- a/mobx_codegen/test/generator_usage_test.g.dart
+++ b/mobx_codegen/test/generator_usage_test.g.dart
@@ -6,7 +6,7 @@ part of 'generator_usage_test.dart';
 // StoreGenerator
 // **************************************************************************
 
-// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
+// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
 
 mixin _$TestStore on _TestStore, Store {
   Computed<String>? _$fieldsComputed;
diff --git a/mobx_codegen/test/mobx_codegen_test.dart b/mobx_codegen/test/mobx_codegen_test.dart
index fab1475c2..dcd029f49 100644
--- a/mobx_codegen/test/mobx_codegen_test.dart
+++ b/mobx_codegen/test/mobx_codegen_test.dart
@@ -47,6 +47,13 @@ void main() {
           './data/valid_output_annotation_store_config_has_to_string.dart');
     });
 
+    test(
+        'generates for a class containing annotations "@protected", "@visibleForTesting" and "visibleForOverriding"',
+        () async {
+      await compareFiles('./data/annotations_test_class_input.dart',
+          './data/annotations_test_class_output.dart');
+    });
+
     createTests([
       const TestInfo(
           description: 'invalid output is handled',
diff --git a/mobx_codegen/test/nested_store.g.dart b/mobx_codegen/test/nested_store.g.dart
index 38a3aed45..6599ebde4 100644
--- a/mobx_codegen/test/nested_store.g.dart
+++ b/mobx_codegen/test/nested_store.g.dart
@@ -6,7 +6,7 @@ part of 'nested_store.dart';
 // StoreGenerator
 // **************************************************************************
 
-// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
+// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
 
 mixin _$NestedStore on _NestedStore, Store {
   late final _$nameAtom = Atom(name: '_NestedStore.name', context: context);
diff --git a/mobx_codegen/test/store_with_custom_context.g.dart b/mobx_codegen/test/store_with_custom_context.g.dart
index 6558952e0..aaea811b4 100644
--- a/mobx_codegen/test/store_with_custom_context.g.dart
+++ b/mobx_codegen/test/store_with_custom_context.g.dart
@@ -6,7 +6,7 @@ part of 'store_with_custom_context.dart';
 // StoreGenerator
 // **************************************************************************
 
-// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
+// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
 
 mixin _$CustomContextStore on _CustomContextStore, Store {
   late final _$nameAtom =
diff --git a/mobx_codegen/test/templates_test.dart b/mobx_codegen/test/templates_test.dart
index 319a04dcf..734d023d1 100644
--- a/mobx_codegen/test/templates_test.dart
+++ b/mobx_codegen/test/templates_test.dart
@@ -76,7 +76,6 @@ void main() {
           type: 'ReturnType',
           name: 'computedField');
 
-      // ignore: prefer_single_quotes
       expect(template.toString(), equals("""
   Computed<ReturnType>? computedName;
 
@@ -225,29 +224,33 @@ void main() {
   });
 
   group('ActionTemplate', () {
+    final method = MethodOverrideTemplate()
+      ..name = 'myAction'
+      ..returnType = 'ReturnType'
+      ..setTypeParams([
+        TypeParamTemplate(name: 'T'),
+        TypeParamTemplate(name: 'S', bound: 'String')
+      ])
+      ..positionalParams = [ParamTemplate(name: 'arg1', type: 'T')]
+      ..optionalParams = [
+        ParamTemplate(name: 'arg2', type: 'S', defaultValue: '"arg2value"'),
+        ParamTemplate(name: 'arg3', type: 'String')
+      ]
+      ..namedParams = [
+        ParamTemplate(
+            name: 'namedArg1', type: 'String', defaultValue: '"default"'),
+        ParamTemplate(name: 'namedArg2', type: 'int', defaultValue: '3')
+      ];
+
     test('renders properly', () {
       final template = ActionTemplate(
-          storeTemplate: (MixinStoreTemplate()..parentTypeName = 'ParentClass'),
-          method: (MethodOverrideTemplate()
-            ..name = 'myAction'
-            ..returnType = 'ReturnType'
-            ..setTypeParams([
-              TypeParamTemplate(name: 'T'),
-              TypeParamTemplate(name: 'S', bound: 'String')
-            ])
-            ..positionalParams = [ParamTemplate(name: 'arg1', type: 'T')]
-            ..optionalParams = [
-              ParamTemplate(
-                  name: 'arg2', type: 'S', defaultValue: '"arg2value"'),
-              ParamTemplate(name: 'arg3', type: 'String')
-            ]
-            ..namedParams = [
-              ParamTemplate(
-                  name: 'namedArg1', type: 'String', defaultValue: '"default"'),
-              ParamTemplate(name: 'namedArg2', type: 'int', defaultValue: '3')
-            ]));
-
-      // ignore: prefer_single_quotes
+        storeTemplate: (MixinStoreTemplate()..parentTypeName = 'ParentClass'),
+        method: method,
+        hasProtected: false,
+        hasVisibleForOverriding: false,
+        hasVisibleForTesting: false,
+      );
+
       expect(template.toString(), equals("""
     @override
     ReturnType myAction<T, S extends String>(T arg1, [S arg2 = "arg2value", String arg3], {String namedArg1 = "default", int namedArg2 = 3}) {
@@ -259,40 +262,80 @@ void main() {
       }
     }"""));
     });
+
+    test('generates template with "@protected" annotation', () {
+      final template = ActionTemplate(
+        storeTemplate: (MixinStoreTemplate()..parentTypeName = 'ParentClass'),
+        method: method,
+        hasProtected: true,
+        hasVisibleForOverriding: false,
+        hasVisibleForTesting: false,
+      );
+
+      expect(template.toString(), contains('@protected'));
+    });
+
+    test('generates template with "@visibleForOverriding" annotation', () {
+      final template = ActionTemplate(
+        storeTemplate: (MixinStoreTemplate()..parentTypeName = 'ParentClass'),
+        method: method,
+        hasProtected: false,
+        hasVisibleForOverriding: true,
+        hasVisibleForTesting: false,
+      );
+
+      expect(template.toString(), contains('@visibleForOverriding'));
+    });
+
+    test('generates template with "@visibleForTesting" annotation', () {
+      final template = ActionTemplate(
+        storeTemplate: (MixinStoreTemplate()..parentTypeName = 'ParentClass'),
+        method: method,
+        hasProtected: false,
+        hasVisibleForOverriding: false,
+        hasVisibleForTesting: true,
+      );
+
+      expect(template.toString(), contains('@visibleForTesting'));
+    });
   });
 
   group('ObservableFutureTemplate', () {
+    final method = MethodOverrideTemplate()
+      ..name = 'fetchData'
+      ..returnType = 'Future'
+      ..returnTypeArgs = SurroundedCommaList('<', '>', ['T'])
+      ..setTypeParams([
+        TypeParamTemplate(name: 'T'),
+        TypeParamTemplate(name: 'S', bound: 'String')
+      ])
+      ..positionalParams = [
+        ParamTemplate(
+          name: 'arg1',
+          type: 'T',
+        )
+      ]
+      ..optionalParams = [
+        ParamTemplate(name: 'arg2', type: 'S', defaultValue: '"arg2value"'),
+        ParamTemplate(
+          name: 'arg3',
+          type: 'String',
+        )
+      ]
+      ..namedParams = [
+        ParamTemplate(
+            name: 'namedArg1', type: 'String', defaultValue: '"default"'),
+        ParamTemplate(name: 'namedArg2', type: 'int', defaultValue: '3')
+      ];
+
     test('renders properly', () {
       final template = ObservableFutureTemplate(
-          method: (MethodOverrideTemplate()
-            ..name = 'fetchData'
-            ..returnType = 'Future'
-            ..returnTypeArgs = SurroundedCommaList('<', '>', ['T'])
-            ..setTypeParams([
-              TypeParamTemplate(name: 'T'),
-              TypeParamTemplate(name: 'S', bound: 'String')
-            ])
-            ..positionalParams = [
-              ParamTemplate(
-                name: 'arg1',
-                type: 'T',
-              )
-            ]
-            ..optionalParams = [
-              ParamTemplate(
-                  name: 'arg2', type: 'S', defaultValue: '"arg2value"'),
-              ParamTemplate(
-                name: 'arg3',
-                type: 'String',
-              )
-            ]
-            ..namedParams = [
-              ParamTemplate(
-                  name: 'namedArg1', type: 'String', defaultValue: '"default"'),
-              ParamTemplate(name: 'namedArg2', type: 'int', defaultValue: '3')
-            ]));
-
-      // ignore: prefer_single_quotes
+        method: method,
+        hasProtected: false,
+        hasVisibleForOverriding: false,
+        hasVisibleForTesting: false,
+      );
+
       expect(template.toString(), equals("""
   @override
   ObservableFuture<T> fetchData<T, S extends String>(T arg1, [S arg2 = "arg2value", String arg3], {String namedArg1 = "default", int namedArg2 = 3}) {
@@ -300,5 +343,36 @@ void main() {
     return ObservableFuture<T>(_\$future, context: context);
   }"""));
     });
+
+    test('generates template with "@protected" annotation', () {
+      final template = ObservableFutureTemplate(
+        method: method,
+        hasProtected: true,
+        hasVisibleForOverriding: false,
+        hasVisibleForTesting: false,
+      );
+
+      expect(template.toString(), contains('@protected'));
+    });
+    test('generates template with "@visibleForOverriding" annotation', () {
+      final template = ObservableFutureTemplate(
+        method: method,
+        hasProtected: false,
+        hasVisibleForOverriding: true,
+        hasVisibleForTesting: false,
+      );
+
+      expect(template.toString(), contains('@visibleForOverriding'));
+    });
+    test('generates template with "@visibleForTesting" annotation', () {
+      final template = ObservableFutureTemplate(
+        method: method,
+        hasProtected: false,
+        hasVisibleForOverriding: false,
+        hasVisibleForTesting: true,
+      );
+
+      expect(template.toString(), contains('@visibleForTesting'));
+    });
   });
 }