[英]Is a bad practice passing references of a View to a Presenter in MVP Pattern?
I have a big project for Android with Kotlin using the MVP pattern, and I'm starting to struggle to do Unit Tests (testing the presenters mocking the view interfaces). 我有一个关于Android的大项目,Kotlin使用MVP模式,我开始努力进行单元测试(测试演示者模拟视图接口)。 The reason is I'm passing view references to my functions in the presenters and it's really bad having to mock them, example:
原因是我在演示者中传递对我的函数的视图引用,并且必须模拟它们真的很糟糕,例如:
My code would look like: 我的代码看起来像:
class MainActivity : Activity(), MainActivityView {
@BindView(R.id.numberTV)
lateinit var numberTV : AppCompatTextView
private val mainActivityPresenter = MainActivityPresenter(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainActivityPresenter.onCreate()
}
override fun showNumber() {
mainActivityPresenter.showNumber(numberTV, 22)
}
}
interface MainActivityView {
fun showNumber()
}
class MainActivityPresenter(private val mainActivityView: MainActivityView) {
fun showNumber(numberTV: AppCompatTextView, number: Int) {
numberTV.text = if (number < 0) {
"Not compatible"
} else if (number < 10) {
number.toString()
} else {
"9+"
}
}
fun onCreate() {
mainActivityView.showNumber()
}
}
My current problem is, when I'm testing the function showNumber(AppCompatTextView, Int)
with Mockito in Unit Tests , I should mock the view just to pass the test (as it can't be null). 我当前的问题是,当我在单元测试中使用Mockito测试函数
showNumber(AppCompatTextView, Int)
时,我应该模拟视图以传递测试(因为它不能为null)。
Which one would be a better approach to do Unit Tests here? 哪一个是更好的方法来进行单元测试?
My thoughts are: 我的想法是:
numberTV: AppCompatTextView
from the MainActivityPresenter
, such as mainActivityPresenter.getBindViews().mainIV
MainActivityPresenter
numberTV: AppCompatTextView
,例如mainActivityPresenter.getBindViews().mainIV
"Not compatible"
, number.toString()
or "9+"
) from the presenter, although sometimes the requirements require to do logic between more than view (2+) "Not compatible"
, number.toString()
或"9+"
),尽管有时需要在视图之外执行逻辑(2+) What would you do? 你会怎么做?
EDIT 编辑
I would like to point out that not passing any View
to the presenter, might be an issue with asynchronous calls. 我想指出,不将任何
View
传递给演示者,可能是异步调用的问题。
Example: setting an image with Glide: 示例:使用Glide设置图像:
internal fun showImageAccordingToCache(cachedSplashScreenUri: String?, mainImageView: ImageView) {
Glide.with(context)
/**
* Save in cache, but we say to load it from cache. Otherwise it will throw an error
*/
.setDefaultRequestOptions(defaultDiskStrategy()
.onlyRetrieveFromCache(true))
.load(cachedSplashScreenUri)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
/**
* And when that error is thrown, we preload the image for the next time.
*/
activityViewPresenter.showLogo()
loadImageInCache(cachedSplashScreenUri)
return true
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
return false
}
})
.into(mainImageView)
}
I think you should not pass views to your presenter. 我认为你不应该向你的演示者传递意见。 Your presenter should call mainActivityView methods to show the required Data.
您的演示者应该调用mainActivityView方法来显示所需的数据。 This should be the method in your mainAcitivityView
这应该是mainAcitivityView中的方法
override fun showNumber(number: String) {
numberTV.text = number
}
And you should call this from your presenter like this 你应该像这样从你的演示者那里打电话
fun onCreate() {
showNumber(22)
}
fun showNumber(number: Int) {
numberString:String = if (number < 0) {
"Not compatible"
} else if (number < 10) {
number.toString()
} else {
"9+"
}
mainActivityView.showNumber(numberString:String)
}
It's normal to have an interface View in your presenter. 在演示者中有一个界面View是很正常的。 In your case MainActivityView is the interface that represents the contract your Activity must comply to.
在您的情况下,MainActivityView是表示您的Activity必须遵守的合同的接口。 You'd normally pass that view in the constructor of the presenter (what you're already doing) on by using dagger to inject it into the presenter.
您通常会使用dagger将其注入演示者,从而在演示者的构造函数(您已经在做的事情)中传递该视图。
Now this, is not very usual: 现在这个,不常见:
fun showNumber(numberTV: AppCompatTextView, number: Int) {
numberTV.text = if (number < 0) {
"Not compatible"
} else if (number < 10) {
number.toString()
} else {
"9+"
}
}
Now the presenter knows about "Android SDK components" which is not a good thing. 现在主持人知道“Android SDK组件”这不是一件好事。 In this case what you should be doing is this:
在这种情况下,你应该做的是:
fun showNumber(number: Int) {
if (number < 0) {
mainActivityView.setNumberText("Not compatible");
} else if (number < 10) {
mainActivityView.setNumberText(number.toString());
} else {
mainActivityView.setNumberText("9+");
}
}
To test this you would mock the view and see if depending on number each of those methods were actually called.(In java). 为了测试这个,你会模拟视图,看看是否依赖于数字实际调用了这些方法。(在java中)。
@Mock
MainActivityView view;
@Test
public fun shouldShowCorrectNumber() {
int number = 10;
presenter.showNumber(number);
verify(view).showNumber("9+");
}
As for async calls, what I usually see when using the Glide library is that it is used in the activity, not in the presenter. 对于异步调用,我在使用Glide库时通常会看到它在活动中使用,而不是在演示者中使用。 Other types of async calls might go in other layers, for example.
例如,其他类型的异步调用可能会出现在其他层中。 I usually see network calls in the Interactor layer with callbacks to the presenter:
我通常会在Interactor层看到网络调用,并对演示者进行回调:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.