Giter VIP home page Giter VIP logo

Comments (15)

realdadfish avatar realdadfish commented on June 22, 2024 1

from magnet.

sergejsha avatar sergejsha commented on June 22, 2024 1

I suspect, you don't really want to use MockableScope in production at all 😉 Use it for tests only. If you have a TestApp, you can create it there.

from magnet.

realdadfish avatar realdadfish commented on June 22, 2024

Hrm... looking at MagnetScope I have the feeling that it might be as easy as to remove the `if (factory == null)´ condition...? What would be the consequences of that?

    @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();
        }
       ...
   }

from magnet.

realdadfish avatar realdadfish commented on June 22, 2024

Ready further there I see there is a lot more going on - somehow I think I need to suppress the call to T object = factory.create(this); when there is a deepInstance available, but right now that happens unconditionally. Don't know, you're the expert here :)

from magnet.

sergejsha avatar sergejsha commented on June 22, 2024

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 (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.

from magnet.

realdadfish avatar realdadfish commented on June 22, 2024

from magnet.

realdadfish avatar realdadfish commented on June 22, 2024

from magnet.

sergejsha avatar sergejsha commented on June 22, 2024

What it there was a @TestInstance annotation, which generates a "preferred" factory if both @Instance and @TestInstance are available in classpath?

from magnet.

sergejsha avatar sergejsha commented on June 22, 2024

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 createSubscope() method. This should allow having a single "flat" mocked scope at one place.

from magnet.

realdadfish avatar realdadfish commented on June 22, 2024

Yep! Closing this therefor.

from magnet.

realdadfish avatar realdadfish commented on June 22, 2024

Coming back to this, I guess the @TestInstance annotation might be a good idea actually. The "new" use case is that we want to add BDD acceptance tests to the app and these tests naturally test things not always in isolation, but have a more integrative character. In the end all you really want is to mock out / replace the storage / API layer and leave the rest wired as-is.

from magnet.

sergejsha avatar sergejsha commented on June 22, 2024

I have been thinking about this after our discussion and I see some disadvantages of having @TestInstance annotation like inability to exclude some instance in tests configuration, inability to have two or more different test instances of same type for two or more different tests and there can be even more.

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 MockableScope capable of providing mock instances from given MockedInstanceProvider. It's enough to wrap your original root scope usually created in the App with this mocked one and it will work out down the whole scope hierarchy. Each get-call will be passed to the provider first, and if provider doesn't return an instance, original scope will the queried.

Thus everything you want to mock should be returned from MockedInstanceProvider.

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 has<Cardinality>() methods to the provider and only request origin if corresponding method returns true.

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.

from magnet.

realdadfish avatar realdadfish commented on June 22, 2024

Hrm... that sounds like a solution. There could then be a MockedInstanceProvider implementation for production that always returns null to keep the overhead low. Let's see how I can wire this in. Thanks!

from magnet.

sergejsha avatar sergejsha commented on June 22, 2024

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. MockableScope implements decorator pattern and adds a lookup for mocked instances in given MockedInstanceProvider. It's enough to wrap original production root scope for making the whole hierarchy mockable, because MockableScope overrides createSubscope() method and returns mockable scopes for all subscopes.

Additional notes on MockedInstanceProvider. In contrast to production scopes, provider has a flat hierarchy and it ignores (or can ignore) scoping attribute. Depending on your scope/instances configuration you might need to return the same mocked instance or a new instance from its getters.

from magnet.

stale avatar stale commented on June 22, 2024

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.

from magnet.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.