I tried to use TDD and got following code:
public class ViewModel extends BaseObservable {
private final Subscription subscription;
public ReleasesViewModel(Observable<List<Data>> model) {
subscription = model.subscribe(this::setData);
}
public void destroy() { //method is not under test
subscription.unsubscribe();
}
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
}
and my test for :
public class ViewModelTest {
#Test
public void getData() {
BehaviorSubject<List<Data>> observable = BehaviorSubject.create();
ViewModel viewModel = new ViewModel(observable);
List<Data> dataList = Arrays.asList(mock(Data.class), mock(Data.class));
observable.onNext(dataList);
assertTrue(viewModel.getData().equals(dataList));
}
}
The question is following:
I should verify that subscription.unsubscribe(); will be called to release resources, I can wrap subscription in some wrapper and inject dependency via constructor but I feel like I can violate an encapsulation of ViewModel class. After a lot of googling I did not find any clue for the case of verifying memory releasing in TDD practice. Can some one point me to some "best practices" for this case.
maybe you have some example of proper mvvm testing
This is what I'd do using Mockito (Just my preference, any other Mocking Framework will do either...):
public class ViewModelTest {
#Rule
public MockitoRule rule = MockitoJUnit.rule();
#Mock
BehaviorSubject<List<Data>> observable;
#Mock
Subscription subscription;
List<Data> data = Collections.emptyList();
#Test
public void Constructor_CalledWithObservable_subcribesSetDataMethod() {
// prepare
when(model.subscribe()).thenRetun(subscription);
// act
ViewModel viewModel = new ViewModel(observable);
// assert
verify(observable).subscribe(ViewModel::setData);
}
#Test
public void destroy_unsubscribes() {
// prepare
when(model.subscribe()).thenRetun(subscription);
ViewModel viewModel = new ViewModel(observable);
// act
viewModel.destroy();
// assert
verify(subscription).unsubscribe();
}
}
Related
I need to do unit testing of methods of Singleton class which internally uses RxJava Singles, and used PowerMock test framework to mock static class and methods. I tried various method to mock Schedulers.io() and AndroidSchedulers.mainThread() methods but it's not working. I'm getting java.lang.NullPointerException error at line .subscribeOn(Schedulers.io()) inside UserApi.verifyUserData() method.
Singleton Class UserApi (Class under Test)
final public class UserApi {
private CompositeDisposable compositeDisposable;
private String userID;
//private final SchedulerProvider schedulerProvider;
private UserApi(String userId) {
super();
this.userID = userId;
//this.schedulerProvider = schedulerProvider;
}
public static UserApi getInstance() {
return SingletonHolder.sINSTANCE;
}
private static final class SingletonHolder {
private static final UserApi sINSTANCE;
static {
String uuid = UUID.randomUUID().toString();
sINSTANCE = new UserApi(uuid);
}
}
// Rest Api call
public void verifyUserData(byte[] doc, byte[] img) {
this.compositeDisposable = new CompositeDisposable();
String docStr = Base64.encodeToString(doc, Base64.NO_WRAP);
String imgStr = Base64.encodeToString(img, Base64.NO_WRAP);
final Disposable apiDisposable = IdvManager.getInstance().getUserManager().verifyUserData(docStr, imgStr)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<JsonObject>() {
#Override
public void accept(JsonObject verifyResponse) throws Exception {
pollResult();
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable error) throws Exception {
// handle error code...
}
});
this.compositeDisposable.add(apiDisposable);
}
private void pollResult() {
// code here...
}
}
UserManager Class and Interface
public interface UserManager {
Single<JsonObject> verifyUserData(String docStr, String imgStr);
}
final class UserManagerImpl implements UserManager {
private final UserService userService;
UserManagerImpl(final Retrofit retrofit) {
super();
this.userService = retrofit.create(UserService.class);
}
#Override
public Single<JsonObject> verifyUserData(String docStr, String imgStr) {
// Code here...
}
}
Unit Test
#RunWith(PowerMockRunner.class)
#PrepareForTest({IdvManager.class, Base64.class, Schedulers.class, AndroidSchedulers.class, UserApi.class})
public class UserApiTest {
#Mock
public UserManager userManager;
#Mock
private Handler handler;
private IdvManager idvManager;
private Schedulers schedulers;
private UserApi spyUserApi;
private TestScheduler testScheduler;
private String userID;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
testScheduler = new TestScheduler();
handler = new Handler();
PowerMockito.suppress(constructor(IdvManager.class));
// mock static
PowerMockito.mockStatic(IdvManager.class);
PowerMockito.mockStatic(Schedulers.class);
PowerMockito.mockStatic(AndroidSchedulers.class);
PowerMockito.mockStatic(Base64.class);
// Create mock for class
idvManager = PowerMockito.mock(IdvManager.class);
schedulers = PowerMockito.mock(Schedulers.class);
PowerMockito.when(IdvManager.getInstance()).thenReturn(IdvManager);
when(idvManager.getUserManager()).thenReturn(userManager);
spyUserApi = PowerMockito.spy(UserApi.getInstance());
// TestSchedulerProvider testSchedulerProvider = new TestSchedulerProvider(testScheduler);
when(Base64.encodeToString((byte[]) any(), anyInt())).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return java.util.Base64.getEncoder().encodeToString((byte[]) invocation.getArguments()[0]);
}
});
when(schedulers.io()).thenReturn(testScheduler);
when(AndroidSchedulers.mainThread()).thenReturn(testScheduler);
userID = UUID.randomUUID().toString();
}
#After
public void clearMocks() {
//Mockito.framework().clearInlineMocks();
}
#Test
public void verifyUserData_callsPollResult_returnsResponse() {
// Input
String docStr = "iVBORw0KGgoAAAANSUhEUgAAAJ4AAACeCAYAAADDhbN7AA.....";
// Output
JsonObject verifyResponse = new JsonObject();
verifyResponse.addProperty("status", "Response created");
doReturn(Single.just(verifyResponse)).when(userManager).verifyUserData(docStr, docStr);
// spy method call
spyUserApi.verifyUserData(docFrontArr, docFrontArr);
testScheduler.triggerActions();
// assert
verify(userManager).verifyUserData(docStr, docStr);
}
}
Error
java.lang.NullPointerException
at com.rahul.manager.UserApi.verifyUserData(UserApi.java:60)
at com.rahul.manager.UserApiTest.verifyUserData_callsPollResult_returnsResponse(UserApiTest.java:171)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
I'm not sure whether i can test methods of Singleton class by spying on real instance of Singleton class using PowerMock.
Testing your code is complex because it's not testable and it's not extensible. It contains hardcoded dependencies everywhere (e.g. user id, handler, several singletons).
If you decide to use another id generation approach or another handler, you won't be able to do this without rewriting whole class.
Instead of hardcoding dependencies, ask for them in constructor (for mandatory dependencies) or setters (for optional ones).
This will make your code extensible and testable. After you do this, you will see your class contains several responsibilities, after moving them into separate classes, you will get much better picture :-)
For example:
public UserApi(String userId, Handler handle) {
this.userId = userId;
this.handler = handler;
}
Schedulers.io() is a static method, so you need to use mockStatic (which you did) and define the related mock accordingly.
I rearranged your setup method a bit, to improve the readability and fixed the mistake. You do not need an instance of Schedulers (The variable you named schedulers).
Probably a simple typo you made, as you did the right thing for Base64 and AndroidSchedulers.
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
testScheduler = new TestScheduler();
handler = new Handler();
// mock for instance of `IdvManager`
PowerMockito.suppress(constructor(IdvManager.class));
idvManager = PowerMockito.mock(IdvManager.class);
when(idvManager.getUserManager()).thenReturn(userManager);
// mock for `IdvManager` class
PowerMockito.mockStatic(IdvManager.class);
PowerMockito.when(IdvManager.getInstance()).thenReturn(idvManager);
// mock for `Schedulers` class
PowerMockito.mockStatic(Schedulers.class);
when(Schedulers.io()).thenReturn(testScheduler);
// spy for instance of `UserApi`
spyUserApi = PowerMockito.spy(UserApi.getInstance());
// mock for `Base64` class
PowerMockito.mockStatic(Base64.class);
when(Base64.encodeToString((byte[]) any(), anyInt())).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return java.util.Base64.getEncoder().encodeToString((byte[]) invocation.getArguments()[0]);
}
});
// mock for `AndroidSchedulers` class
PowerMockito.mockStatic(AndroidSchedulers.class);
when(AndroidSchedulers.mainThread()).thenReturn(testScheduler);
userID = UUID.randomUUID().toString();
}
However the NPE is missing the part that actually indicates its failing from this, consider adding it if that does not solve your problem.
I have a custom Dagger Scope.
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface FeatureScope {
}
I have a Component/Module that provides a Presenter (any UI element will really work here) that is tied to this scope.
#FeatureScope
#Component(modules = {
CustomScopedModule.class
})
public interface CustomScopedComponent {
}
#Module
public class CustomScopedModule {
#FeatureScope
#Provides
Presenter providePresenter() {
return new Presenter();
}
}
I know that it is up to me to create/destroy this custom scope when it is no longer needed. So in my application I have:
public class MyApplication extends Application {
private CustomScopedComponent customScopedComponent;
public CustomScopedComponent getCustomScopedComponent() {
if (customScopedComponent == null) {
customScopedComponent = DaggerCustomScopedComponent.builder()
.contactsModule(new CustomScopedModule())
.build();
}
return customScopedComponent;
}
public void finishedWithCustomScopedComponent() {
customScopedComponent = null;
}
However I now need to "cleanup" after taking Component (and Presenter) out of scope. I can't just set customScopedComponent to null. I also need to call cleanup() on my Presenter:
class Presenter {
public void cleanup() {
// Dispose of all the things
// Release any DB connections
}
...
}
What is the best way to handle/create a call path to handle this? I see that Dagger doesn't really have any tools for this and that I need to roll my own solution Discussion
So I thought at least I could add a method to the module:
#Module
public class CustomScopedModule {
#FeatureScope
#Provides
Presenter providePresenter() {
return new Presenter();
}
public void cleanup() {
// call Presenter.cleanup();
}
}
And call it when destroying the component:
public class MyApplication extends Application {
private CustomScopedComponent customScopedComponent;
private CustomScopedModule customScopedModule;
...
public void finishedWithCustomScopedComponent() {
customScopedModule.cleanup();
customScopedComponent = null;
}
}
But my question is, how can the module's cleanup() method get access to the presenter? Only thing I can think of is just saving the presenter as a field inside the module:
#Module
public class CustomScopedModule {
private Presenter presenter;
#FeatureScope
#Provides
Presenter providePresenter() {
presenter = new Presenter(); // save instance to cleanup later
return presenter;
}
public void cleanup() {
if (presenter != null) {
presenter.cleanup();
}
}
}
This seems ugly and very un-Dagger to me.
I am recently trying to learn Android and I am very new to backend knowledge e.g. threading and stuff. I got Room figured out and try to integrate it with front end component. So, I am not worried how front end adapting the data I want it to present. I have the problem trying to design and implement the integration in a clean way using thread and trying to implement threading since I am new to it.
Here is my code.
Database.class
#Database(entities = {Groups.class, Member.class}, version = 1, exportSchema
= false)
public abstract class DatabaseConfig extends RoomDatabase {
private static final String DATABASE_NAME = "db";
private static DatabaseConfig INSTANCE;
public abstract GroupDao groupDao();
public abstract MemberDao memberDao();
public static DatabaseConfig getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (DatabaseConfig.class) {
if (INSTANCE == null) {
INSTANCE =
Room.databaseBuilder(context.getApplicationContext(),
DatabaseConfig.class, DATABASE_NAME)
.addCallback(DatabaseCallBack)
.build();
}
}
}
return INSTANCE;
}
private static RoomDatabase.Callback DatabaseCallBack =
new RoomDatabase.Callback(){
#Override
public void onOpen (#NonNull SupportSQLiteDatabase db){
super.onOpen(db);
}
};
}
GroupRepo.class
public class GroupRepo {
private final GroupDao groupDao;
//ExecutorService es = Executors.newSingleThreadExecutor();
public GroupRepo (Context context){
DatabaseConfig db = DatabaseConfig.getDatabase(context);
groupDao = db.groupDao();
}
public List<Groups> getAllGroups(){
/*
So my idea is to have some sort of threading implement from here
and use executor.run() to run my query and capture data
*/
}
}
Groups.class
#Dao
public interface GroupDao {
#Query("SELECT * from groups")
List<Groups> getAllGroups();
#Query("Select * from groups where groups.id = :groupsId")
GroupAllMembers getAllMember(int groupsId);
#Insert
void insert(Groups... groups);
#Delete
void delete(Groups groups);
}
I am not going to post my Entity class since my intention is not about that. I am fairly new to background threads. Please help and ideally provide some example to help me understand.
There are two ways to handle this: if you are doing the query on the database for UI view, I'd recommend your Day return LiveData> and then put that inside of a viewmodel. All of this is covered in Android docs.
If you are doing it in a service or don't want to interact with UI simply do this:
Thread(Runnable() {
#Override
public void run() {
// Do your stuff here with Room
}
}).start()
I have a tricky situation. I am using MVP architecture for android but thats not important. I have a class called DoStandardLoginUsecase that basically just connects to a server with login info and gets a access token. i am trying to test it. But the problem is the context that i am passing in to it so i can initialize dagger.
public class DoStandardLoginUsecase extends BaseUseCase {
#Inject
UserDataRepository mUserDataRepo;
private StandardLoginInfo loginInfo;
public DoStandardLoginUsecase(Context context) {
/* SEE HERE I AM USING A APPLICATION CONTEXT THAT I PASS TO DAGGER
*/
((MyApplication)context).getPresenterComponent().inject(this);
}
#Override
public Observable<Login> buildUseCaseObservable() {
return mUserDataRepo.doStandardLogin(loginInfo);
}
public void setLoginInfo(StandardLoginInfo loginInfo) {
this.loginInfo = loginInfo;
}
}
and here is the test i have so far:
public class DoStandardLoginUsecaseTest {
DoStandardLoginUsecase standardLoginUsecase;
StandardLoginInfo fakeLoginInfo;
TestObserver<Login> subscriber;
MockContext context;
#Before
public void setUp() throws Exception {
//now when i create the object since its a mock context it will fail when it tries to call real things as these are stubs. So how do i test this object. how do i create an instance of this object ? I am willing to use [daggerMock][1] if that helps also.
standardLoginUsecase = New DoStandardLoginUsecase(context);
fakeLoginInfo = new StandardLoginInfo("fred#hotmail.com","Asdfgh4534");
subscriber = TestObserver.create();
}
#Test
public void buildUseCaseObservable(){
standardLoginUsecase.seLoginInfo(fakeLoginInfo);
standardLoginUsecase.buildUseCaseObservable().subscribe(subscriber);
subscriber.assertNoErrors();
subscriber.assertSubscribed();
subscriber.assertComplete();
}
}
I would do the test like this:
public class DoStandardLoginUsecaseTest {
private DoStandardLoginUsecase target;
private MyApplication contextMock;
#Before
public void beforeEach() {
contextMock = Mockito.mock(MyApplication.class);
// Note that you need to mock the getPresenterComponent
// but I don't know what it returns.
target = new DoStandardLoginUsecase(contextMock);
}
#Test
public void buildUseCaseObservable() {
UserDataRepository userDataMock = Mockito.mock(UserDataRepository.class);
StandardLoginInfo loginInfoMock = Mockito.mock(StandardLoginInfo.class);
target.mUserDataRepo = userDataMock;
target.setLoginInfo(loginInfoMock);
Observable<Login> expected = // create your expected test data however you like...
Mockito.when(userDataMock.doStandardLogin(loginInfoMock)).thenReturn(expected);
Observable<Login> actual = target.buildUseCaseObservable();
Assert.areSame(actual, expected);
}
}
We are building an application which uses Spring Boot. We write unit tests using TestNG and Mockito. However I find it pretty annoying to write when(...) configuration, I would like to use real components instead. I started to use #Spy components instead of mocks and this works pretty well until I need to put a Spy into a Spy. I'd like to avoid loading a Spring Context if possible, because creation of the context is very slow it looks like overkill for me to load it for at max 5 classes.
Is there any way, how could I use real code instead of Mocks and not loading whole Spring context? Or is my approach wrong at all and I should mock out all other classes then the tested one?
The other way to do this and may take some modifying of code on your end is to do it by constructor injection instead of field injection. Basically taking away any need of the spring context for testing. so the same from the other answer
Class to test
#Service
public class RecordServiceImpl implements RecordService
{
private final RecordRepository recordRepository;
#Autowired
public RecordServiceImpl(RecordRepository recordRepository)
{
this.recordRepository = recordRepository;
}
public Record find(String id)
{
return recordRepository.findOne(id);
}
public List<Record> findAll()
{
return recordRepository.findAll();
}
#Transactional
public Record save(Record record)
{
record.setRecordStatus("F");
return recordRepository.save(record);
}
}
Test Case
//#RunWith(SpringJUnit4ClassRunner.class)
//#ContextConfiguration(classes = {RecordServiceTestConfig.class})
public class RecordServiceTest
{
// #Autowired
private RecordRepository recordRepository = Mockito.mock(RecordRepository.class);
// #Autowired
private RecordService recordService;
#Before
public void setup()
{
Mockito.reset(recordRepository);
recordService = new RecordServiceImpl(recordRepository);
}
#Test
public void testFind()
{
Mockito.when(recordRepository.findOne(Mockito.anyString())).thenReturn(null);
Record record = recordService.find("1");
Assert.assertNull(record);
Mockito.verify(recordRepository, Mockito.times(1)).findOne(Mockito.eq("1"));
}
#Test
public void testSave()
{
Mockito.when(recordRepository.save(Mockito.any(Record.class)))
.thenAnswer(new Answer<Record>()
{
#Override
public Record answer(InvocationOnMock invocation) throws Throwable
{
Record record = (Record) invocation.getArguments()[0];
Assert.assertEquals("F", record.getRecordStatus());
return record;
}
});
Record record = new Record();
record = recordService.save(record);
Assert.assertNotNull(record);
Mockito.verify(recordRepository, Mockito.times(1)).save(Mockito.eq(record));
}
#Test
public void findAll()
{
Mockito.when(recordRepository.findAll()).thenReturn(new ArrayList<Record>());
List<Record> records = recordService.findAll();
Assert.assertNotNull(records);
Assert.assertEquals(0, records.size());
Mockito.verify(recordRepository, Mockito.times(1)).findAll();
}
}
I think your looking for like this with the use of #ContextConfiguration and #Configuration
Class to test
#Service
public class RecordServiceImpl implements RecordService
{
#Autowired
private RecordRepository recordRepository;
public Record find(String id)
{
return recordRepository.findOne(id);
}
public List<Record> findAll()
{
return recordRepository.findAll();
}
#Transactional
public Record save(Record record)
{
record.setRecordStatus("F");
return recordRepository.save(record);
}
}
Test Class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {RecordServiceTestConfig.class})
public class RecordServiceTest
{
#Autowired
private RecordRepository recordRepository;
#Autowired
private RecordService recordService;
#Before
public void setup()
{
Mockito.reset(recordRepository);
}
#Test
public void testFind()
{
Mockito.when(recordRepository.findOne(Mockito.anyString())).thenReturn(null);
Record record = recordService.find("1");
Assert.assertNull(record);
Mockito.verify(recordRepository, Mockito.times(1)).findOne(Mockito.eq("1"));
}
#Test
public void testSave()
{
Mockito.when(recordRepository.save(Mockito.any(Record.class)))
.thenAnswer(new Answer<Record>()
{
#Override
public Record answer(InvocationOnMock invocation) throws Throwable
{
Record record = (Record) invocation.getArguments()[0];
Assert.assertEquals("F", record.getRecordStatus());
return record;
}
});
Record record = new Record();
record = recordService.save(record);
Assert.assertNotNull(record);
Mockito.verify(recordRepository, Mockito.times(1)).save(Mockito.eq(record));
}
#Test
public void findAll()
{
Mockito.when(recordRepository.findAll()).thenReturn(new ArrayList<Record>());
List<Record> records = recordService.findAll();
Assert.assertNotNull(records);
Assert.assertEquals(0, records.size());
Mockito.verify(recordRepository, Mockito.times(1)).findAll();
}
}
Test Class Configuration
#Configuration
public class RecordServiceTestConfig
{
#Bean
public RecordService recordService()
{
return new RecordServiceImpl();
}
#Bean
public RecordRepository recordRepository()
{
return Mockito.mock(RecordRepository.class);
}
}
the entire test class took 714ms to run the findAll test took 1ms.
If you are looking to configure your testcase using testng with Spring then you to mention
#ContextConfiguration(locations={
"/context.xml","/test-context.xml"})
at class level to load you spring file and extends your class org.springframework.test.context.testng.AbstractTestNGSpringContextTests
Sample
https://dzone.com/articles/spring-testing-support-testng