jjzjj

android - Realm 单元测试

coder 2023-12-13 原文

我正在尝试对 Realm 及其交互进行单元测试,但进展不太顺利。我已经包含了所有依赖项并不断出现模糊的失败,下面是我的 Helper 类代码,它是 Realm 的包装器。

问题

  1. 这是测试 Realm 的正确方法吗?

  2. 我如何测试应用沙盒中的数据,只能通过 UI/Instrumentation 测试来测试该数据吗?

  3. 我目前(如下)收到错误消息,之前收到“Powermock 零参数构造函数不存在”

GitHub repo

下面是我的单元测试的当前代码:

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, application = CustomApplicationTest.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "javax.crypto.","java.security.*"})
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest({Realm.class, RealmConfiguration.class,
    RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class DatabaseHelperTest {

@Rule
public PowerMockRule rule = new PowerMockRule();

private DatabaseHelper dB;

private Realm realmMock;


@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

    mockStatic(Realm.class);
    mockStatic(RealmConfiguration.class);
    mockStatic(RealmCore.class);
    mock(DatabaseHelper.class);

    final Realm mockRealm = PowerMockito.mock(Realm.class);
    realmMock = mockRealm;

    final RealmConfiguration mockRealmConfig = PowerMockito.mock(RealmConfiguration.class);

    doNothing().when(RealmCore.class);
    RealmCore.loadLibrary(any(Context.class));

    whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig);
    when(Realm.getInstance(any(RealmConfiguration.class))).thenReturn(mockRealm);
    when(Realm.getDefaultInstance()).thenReturn(mockRealm);

    when(Realm.getDefaultInstance()).thenReturn(realmMock);

    when(realmMock.createObject(Person.class)).thenReturn(new Person());

    Person person = new Person();
    person.setId("2");
    person.setName("Jerry");
    person.setAge("25");

    Person person2 = new Person();
    person.setId("3");
    person.setName("Tom");
    person.setAge("22");

    List<Person> personsList = new ArrayList<>();
    personsList.add(person);
    personsList.add(person2);

    RealmQuery<Person> personRealmQuery = mockRealmQuery();
    when(realmMock.where(Person.class)).thenReturn(personRealmQuery);

    RealmResults<Person> personRealmResults = mockRealmResults();
    when(realmMock.where(Person.class).findAll()).thenReturn(personRealmResults);
    when(personRealmResults.iterator()).thenReturn(personsList.iterator());
    when(personRealmResults.size()).thenReturn(personsList.size());

    when(realmMock.copyFromRealm(personRealmResults)).thenReturn(personsList);

    realmMock = mockRealm;
    dB = new DatabaseHelper(realmMock);
}


@Test
public void insertingPerson(){
    doCallRealMethod().when(realmMock).executeTransaction(any(Realm.Transaction.class));

    Person person = mock(Person.class);
    when(realmMock.createObject(Person.class)).thenReturn(person);

    dB.putPersonData();

    verify(realmMock, times(1)).createObject(Person.class);
    verify(person, times(1)).setId(anyString());
}


@Test
public void testExistingData(){
    List<Person> personList = dB.getPersonList();
    //NPE if checking person object properties i.e name, id. Only list size is available why?
    Assert.assertEquals(2, personList.size());

}

@SuppressWarnings("unchecked")
private <T extends RealmObject> RealmQuery<T> mockRealmQuery() {
    return mock(RealmQuery.class);
}

@SuppressWarnings("unchecked")
private <T extends RealmObject> RealmResults<T> mockRealmResults() {
    return mock(RealmResults.class);
}

错误:

org.mockito.exceptions.misusing.NotAMockException: 
Argument passed to verify() is of type Realm$$EnhancerByMockitoWithCGLIB$$317bc746 and is not a mock!
Make sure you place the parenthesis correctly!
See the examples of correct verifications:
verify(mock).someMethod();
verify(mock, times(10)).someMethod();
verify(mock, atLeastOnce()).someMethod();

at com.appstronomy.realmunittesting.db.DatabaseHelperTest.insertingPerson(DatabaseHelperTest.java:133)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

最佳答案

Is this the correct way of testing Realm?

如何遵循官方测试。同时instrumentation tests seem easy , unit test are quite involved :

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class ExampleActivityTest {
    // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    private Realm mockRealm;
    private RealmResults<Person> people;

    @Before
    public void setup() throws Exception {

        // Setup Realm to be mocked. The order of these matters
        mockStatic(RealmCore.class);
        mockStatic(RealmLog.class);
        mockStatic(Realm.class);
        mockStatic(RealmConfiguration.class);
        Realm.init(RuntimeEnvironment.application);

        // Create the mock
        final Realm mockRealm = mock(Realm.class);
        final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class);

        // TODO: Better solution would be just mock the RealmConfiguration.Builder class. But it seems there is some
        // problems for powermock to mock it (static inner class). We just mock the RealmCore.loadLibrary(Context) which
        // will be called by RealmConfiguration.Builder's constructor.
        doNothing().when(RealmCore.class);
        RealmCore.loadLibrary(any(Context.class));


        // TODO: Mock the RealmConfiguration's constructor. If the RealmConfiguration.Builder.build can be mocked, this
        // is not necessary anymore.
        whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig);

        // Anytime getInstance is called with any configuration, then return the mockRealm
        when(Realm.getDefaultInstance()).thenReturn(mockRealm);

        // Anytime we ask Realm to create a Person, return a new instance.
        when(mockRealm.createObject(Person.class)).thenReturn(new Person());

        // Set up some naive stubs
        Person p1 = new Person();
        p1.setAge(14);
        p1.setName("John Young");

        Person p2 = new Person();
        p2.setAge(89);
        p2.setName("John Senior");

        Person p3 = new Person();
        p3.setAge(27);
        p3.setName("Jane");

        Person p4 = new Person();
        p4.setAge(42);
        p4.setName("Robert");

        List<Person> personList = Arrays.asList(p1, p2, p3, p4);

        // Create a mock RealmQuery
        RealmQuery<Person> personQuery = mockRealmQuery();

        // When the RealmQuery performs findFirst, return the first record in the list.
        when(personQuery.findFirst()).thenReturn(personList.get(0));

        // When the where clause is called on the Realm, return the mock query.
        when(mockRealm.where(Person.class)).thenReturn(personQuery);

        // When the RealmQuery is filtered on any string and any integer, return the person query
        when(personQuery.equalTo(anyString(), anyInt())).thenReturn(personQuery);

        // RealmResults is final, must mock static and also place this in the PrepareForTest annotation array.
        mockStatic(RealmResults.class);

        // Create a mock RealmResults
        RealmResults<Person> people = mockRealmResults();

        // When we ask Realm for all of the Person instances, return the mock RealmResults
        when(mockRealm.where(Person.class).findAll()).thenReturn(people);

        // When a between query is performed with any string as the field and any int as the
        // value, then return the personQuery itself
        when(personQuery.between(anyString(), anyInt(), anyInt())).thenReturn(personQuery);

        // When a beginsWith clause is performed with any string field and any string value
        // return the same person query
        when(personQuery.beginsWith(anyString(), anyString())).thenReturn(personQuery);

        // When we ask the RealmQuery for all of the Person objects, return the mock RealmResults
        when(personQuery.findAll()).thenReturn(people);


        // The for(...) loop in Java needs an iterator, so we're giving it one that has items,
        // since the mock RealmResults does not provide an implementation. Therefore, anytime
        // anyone asks for the RealmResults Iterator, give them a functioning iterator from the
        // ArrayList of Persons we created above. This will allow the loop to execute.
        when(people.iterator()).thenReturn(personList.iterator());

        // Return the size of the mock list.
        when(people.size()).thenReturn(personList.size());

        this.mockRealm = mockRealm;
        this.people = people;
    }


    @Test
    public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() {
        doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class));

        // Create activity
        ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get();

        assertThat(activity.getTitle().toString(), is("Unit Test Example"));

        // Verify that two Realm.getInstance() calls took place.
        verifyStatic(times(2));
        Realm.getDefaultInstance();

        // verify that we have four begin and commit transaction calls
        // Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
        //verify(mockRealm, times(4)).executeTransaction(Mockito.any(Realm.Transaction.class));

        // Click the clean up button
        activity.findViewById(R.id.clean_up).performClick();

        // Verify that begin and commit transaction were called (been called a total of 5 times now)
        // Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
        //verify(mockRealm, times(5)).executeTransaction(Mockito.any(Realm.Transaction.class));

        // Verify that we queried for Person instances five times in this run (2 in basicCrud(),
        // 2 in complexQuery() and 1 in the button click)
        verify(mockRealm, times(5)).where(Person.class);

        // Verify that the delete method was called. Delete is also called in the start of the
        // activity to ensure we start with a clean db.
        verify(mockRealm, times(2)).delete(Person.class);

        // Call the destroy method so we can verify that the .close() method was called (below)
        activity.onDestroy();

        // Verify that the realm got closed 2 separate times. Once in the AsyncTask, once
        // in onDestroy
        verify(mockRealm, times(2)).close();
    }

旧答案

https://medium.com/@q2ad/android-testing-realm-2dc1e1c94ee1有一个很好的建议:不要模拟 Realm,而是使用一个临时实例。带有依赖注入(inject)的原始命题:使用

RealmConfiguration testConfig = 
   new RealmConfiguration.Builder().
      inMemory().
      name("test-realm").build();

Realm testRealm = Realm.getInstance(testConfig);

If dependency injection is not possible, you could use

Realm.setDefaultConfiguration(testConfig);

相反,它设置 Realm.getDefaultInstance() 返回的 Realm


编辑:如果您收到 java.lang.IllegalStateException,请记住事先调用 Realm.init(InstrumentationRegistry.getTargetContext()),并将文件放入android-test 目录。 (即:使用仪器测试,而不是单元测试)。

关于android - Realm 单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42195245/

有关android - Realm 单元测试的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  4. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  5. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  6. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  7. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  8. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  9. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  10. ruby-on-rails - 如何调试 cucumber 测试? - 2

    我有:When/^(?:|I)follow"([^"]*)"(?:within"([^"]*)")?$/do|link,selector|with_scope(selector)doclick_link(link)endend我打电话的地方:Background:GivenIamanexistingadminuserWhenIfollow"CLIENTS"我的HTML是这样的:CLIENTS我一直收到这个错误:.F-.F--U-----U(::)failedsteps(::)nolinkwithtitle,idortext'CLIENTS'found(Capybara::Element

随机推荐