Page MenuHomePhabricator

Cannot write to the test database in @dataProvider methods
Closed, DeclinedPublic

Description

[[ https://github.com/sebastianbergmann/phpunit/issues/836 | PHPUnit executes @dataProvider methods before PHPUnit_Framework_TestCase::setupBeforeClass() ]]. We set up the test database much later in the test suite setup sequence, in MediaWikiTestCase::run(). This means that @dataProvider methods cannot call methods that use the test database, like MediaWikiTestCase::getMutableTestUser().

It is possible to work around this by having the data provider pass a closure that returns the desired user object, instead of passing the user object directly. It is also possible to rely on the fact that we hard-code the primary immutable test sysop user to be UTSysop. Neither option is palatable.

It'd be better to ensure that the test database is ready before data provider methods are invoked. Alternately, we could make TestUser objects lazy, so that the users they represent are only inserted into the database on property / method access.

Event Timeline

Alternately, we could make TestUser objects lazy, so that the users they represent are only inserted into the database on property / method access.

A case like https://gerrit.wikimedia.org/r/#/c/289369/20/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php would need to access the user's properties (specifically, the user_id), though, or would have to use the callback method or otherwise construct the row inside the test instead of the data provider somehow.

Krinkle subscribed.

Data providers are meant to be stateless and must be declared as static per PHPUnit design principles. As such, making the database ready in time wouldn't help as one still can't call instance methods from a static method.

Using a lazy object would work. MediaWikiTestCase::getMutableUser and TestUserRegistry seem to already be set up to work this way (it returns a TestUser object on which the developer must call getUser to get the actual User object). However it is not actually lazily loaded/written at the moment.

Alternatively, the specification could be declared declaratively. So that the method is called by the test function instead of the data provider. For example by having the data provider only indicate the requirements (as a constant and/or array) which are then passed to a TestUserRegistry method inside the test function. This is the kind of design that PHPUnit generally tries to encourage.

Alternatively, the specification could be declared declaratively. So that the method is called by the test function instead of the data provider. For example by having the data provider only indicate the requirements (as a constant and/or array) which are then passed to a TestUserRegistry method inside the test function. This is the kind of design that PHPUnit generally tries to encourage.

This description does not tell me what the preferred way would be to make a test such as https://gerrit.wikimedia.org/r/#/c/289369/20/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php that tests that the correct user data is interpolated into the result. Consider also examples where the user ID rather than the name needs to be interpolated somehow.

Alternatively, the specification could be declared declaratively. So that the method is called by the test function instead of the data provider. For example by having the data provider only indicate the requirements (as a constant and/or array) which are then passed to a TestUserRegistry method inside the test function. This is the kind of design that PHPUnit generally tries to encourage.

This description does not tell me what the preferred way would be to make a test such as https://gerrit.wikimedia.org/r/#/c/289369/20/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php that tests that the correct user data is interpolated into the result. Consider also examples where the user ID rather than the name needs to be interpolated somehow.

In such case, the object should be created in the test method and the ID retrieved there to form the said string. Could perhaps be generalised by having a placeholder in the declared string from the data provider and substitute it at run time.

In other words don't use a data provider at all, or write a postprocessor for the data provider that can handle all the possible "expect" data structures. That's... inconvenient.