-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Injection overrides #72
Comments
Hrm... looking at @Nullable
@SuppressWarnings("unchecked")
private <T> T findOrInjectOptional(
@NotNull Class<T> objectType,
@NotNull String classifier,
@Nullable InstanceFactory<T> factory,
byte cardinality
) {
@NotNull InstantiationContext instantiationContext = this.instantiationContext.get();
@NotNull String key = key(objectType, classifier);
InstanceBucket<T> deepInstance = findDeepInstanceBucket(key, factory);
if (factory == null) {
if (deepInstance == null) {
if (cardinality == CARDINALITY_SINGLE) {
throw new IllegalStateException(
String.format(
"Instance of type '%s' (classifier: '%s') was not found in scopes.",
objectType.getName(), classifier));
}
return null;
}
instantiationContext.onDependencyFound(deepInstance.getScopeDepth());
return deepInstance.getSingleInstance();
}
...
} |
Ready further there I see there is a lot more going on - somehow I think I need to suppress the call to |
Hey Alice, you went too deep already 😉 Let me give you some context on this topic first. With Magnet I wanted to avoid the nightmare I've seen in almost each and every project where test/mocked/faked/stubbed injections were setup. Then I realized that when I decompose the code good enough, there is actually no need for any injection in unit tests. A simple pattern like the one below applied to most classes makes the test injection useless. The classes can be easily tested using mocked objects given in constructor. interface BookRepository {
fun loadBook(bookId: String): Single<Book>
}
@Instance(type = BookRepository::class)
internal class DefaultBookRepository(
private val booksApi: BooksApi,
private val booksCache: BooksCache
...
) : BookRepository {
override fun loadBook(bookId: String): Single<Book> { ... }
} It's framework classes causing issues for testing in most the cases. Keeping those classes ( Now back to your question. I suspect you would want to avoid configuring alternative scopes with mixed (real + mocked) instances just because its maintenance is a nightmare. But it looks there is a case where you cannot avoid it. What is this case exactly? Why can't you stay with pure mocking unit testing described above? Having more insides will help me to understand the issue and maybe to come out with an easy solution for it. |
Have a look at the linked Gist. I introduced a ScopeModel that holds and returns the current scope instance across restarts. I then introduced a way to replace the scope in the framework base classes, now all I need is a way for Magnet to allow me to stub this testScope with my own mocked dependencies...
Am 2. März 2019 09:53:20 MEZ schrieb Sergej Shafarenka <notifications@github.com>:
…Hey Alice, you went too deep already 😉 >
>
Let me give you some context on this topic first. With Magnet I wanted
to avoid the nightmare I've seen in almost each and every project
where test/mocked/faked/stubbed injections were setup. Then I realized
that when I decompose the code good enough, there is actually no need
for any injection in unit tests.>
>
A simple pattern like the one below applied to most classes makes the
test injection useless. The classes can be easily tested using mocked
objects given in constructor.>
>
```kotlin>
interface BookRepository {>
fun loadBook(bookId: String): Single<Book>>
}>
>
@instance(type = BookRepository::class)>
internal class DefaultBookRepository(>
private val booksApi: BooksApi,>
private val booksCache: BooksCache>
...>
) : BookRepository {>
override fun loadBook(bookId: String): Single<Book> { ... }>
}>
```>
>
It's framework classes causing issues for testing in most the cases.
Keeping those classes (`Application`, `Activity`, `Fragment`,
`ViewModel` etc) empty helped to solve most those issues. The logic
should always (when possible) be implemented outside of those classes
and the classes should rather work as proxies with no logic in them.
Then the testing will be fun and no mocked injection will be really
required.>
>
Now back to your question. I suspect you would want to avoid
configuring alternative scopes with mixed (real + mocked) instances
just because its maintenance is a nightmare. But it looks there is a
case where you cannot avoid it. What is this case exactly? Why can't
you stay with pure mocking unit testing described above? Having more
insides will help me to understand the issue and maybe to come out with
an easy solution for it.>
>
-- >
You are receiving this because you authored the thread.>
Reply to this email directly or view it on GitHub:>
#72 (comment)
--
Diese Nachricht wurde von meinem Android-Gerät mit K-9 Mail gesendet.
|
And, in case it isn't obvious, I want to mock out ViewModel instances so I can test my UI in isolation :)
Am 2. März 2019 09:53:20 MEZ schrieb Sergej Shafarenka <notifications@github.com>:
…Hey Alice, you went too deep already 😉 >
>
Let me give you some context on this topic first. With Magnet I wanted
to avoid the nightmare I've seen in almost each and every project
where test/mocked/faked/stubbed injections were setup. Then I realized
that when I decompose the code good enough, there is actually no need
for any injection in unit tests.>
>
A simple pattern like the one below applied to most classes makes the
test injection useless. The classes can be easily tested using mocked
objects given in constructor.>
>
```kotlin>
interface BookRepository {>
fun loadBook(bookId: String): Single<Book>>
}>
>
@instance(type = BookRepository::class)>
internal class DefaultBookRepository(>
private val booksApi: BooksApi,>
private val booksCache: BooksCache>
...>
) : BookRepository {>
override fun loadBook(bookId: String): Single<Book> { ... }>
}>
```>
>
It's framework classes causing issues for testing in most the cases.
Keeping those classes (`Application`, `Activity`, `Fragment`,
`ViewModel` etc) empty helped to solve most those issues. The logic
should always (when possible) be implemented outside of those classes
and the classes should rather work as proxies with no logic in them.
Then the testing will be fun and no mocked injection will be really
required.>
>
Now back to your question. I suspect you would want to avoid
configuring alternative scopes with mixed (real + mocked) instances
just because its maintenance is a nightmare. But it looks there is a
case where you cannot avoid it. What is this case exactly? Why can't
you stay with pure mocking unit testing described above? Having more
insides will help me to understand the issue and maybe to come out with
an easy solution for it.>
>
-- >
You are receiving this because you authored the thread.>
Reply to this email directly or view it on GitHub:>
#72 (comment)
--
Diese Nachricht wurde von meinem Android-Gerät mit K-9 Mail gesendet.
|
What it there was a |
Thinking about it, it just occurred to me that there is an even simpler solution: Instead of providing a real Scope instance, I could simply provide a mocked one that directly feeds the needed things into my UI!
Sometimes it helps talking about things :)
Am 2. März 2019 10:30:47 MEZ schrieb Sergej Shafarenka <notifications@github.com>:
…What it there was a ***@***.***` annotation, which generates a
"preferred" factory if both ***@***.***` and ***@***.***` are
available in classpath?
--
You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub:
#72 (comment)
--
Diese Nachricht wurde von meinem Android-Gerät mit K-9 Mail gesendet.
|
Nice, that could be a good fit for you. You might also think of creating a single mocked root Scope (e.g. in TestApp) and then return itself from |
Yep! Closing this therefor. |
Coming back to this, I guess the |
I have been thinking about this after our discussion and I see some disadvantages of having In my opinion a solution should be dealing with a scope which can intercept instance creation, ideally at factory level, and replace instances with mocks. I have no ideal solution for now but I could think of an extension to the solution you already implemented. Here is the Thus everything you want to mock should be returned from class MockableScope(
private val origin: Scope,
private val instanceProvider: MockedInstanceProvider
) : Scope {
override fun <T : Any> getMany(type: Class<T>, classifier: String): MutableList<T> =
instanceProvider.getMany(type, classifier) ?: origin.getMany(type, classifier)
override fun <T : Any> getMany(type: Class<T>): MutableList<T> =
instanceProvider.getMany(type, Classifier.NONE) ?: origin.getMany(type)
override fun <T : Any> getSingle(type: Class<T>, classifier: String): T =
instanceProvider.getSingle(type, Classifier.NONE) ?: origin.getSingle(type)
override fun <T : Any> getOptional(type: Class<T>, classifier: String): T? =
instanceProvider.getOptional(type, classifier) ?: origin.getOptional(type, classifier)
override fun createSubscope(): Scope =
MockableScope(origin.createSubscope(), instanceProvider)
...
}
interface MockedInstanceProvider {
fun <T : Any> getMany(type: Class<T>, classifier: String): MutableList<T>?
fun <T : Any> getOptional(type: Class<T>, classifier: String): T?
fun <T : Any> getSingle(type: Class<T>, classifier: String): T?
} If you need to exclude some instances from been instantiated, you can add Would some something like this work for you? If it works, we can think of adding it to the library in a more generic way later on. |
Hrm... that sounds like a solution. There could then be a |
I suspect, you don't really want to use |
Just to make it more clear. The idea is to keep original scope hierarchy in production code unchanged and to wrap it with a mockable scope hierarchy in tests only. Additional notes on |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Going down the rabbit hole to provide easy testing of Android Activitys and Fragments (see https://gist.github.com/realdadfish/4c65e2da781bebb1479bc4d4624c5fb7) I found myself in the pity position that I cannot override Magnet's use of an annotated factory to create an instance.
While the factory should be used when no specific instance is bound in the scope, I want to be able to explicitly bind a specific (mocked) instance into a scope for testing purposes:
At first I thought this could not work because I annotated the implementation itself:
so I extracted it like so:
but still, Magnet would again use the annotated factory to create an instance of the object, instead of just using the one that I supplied to it.
How can I make Magnet accept my mocked instance?
The text was updated successfully, but these errors were encountered: