Reputation: 3258
I'm trying to understand the unit tests when a database is involved. My classes heavily depends on a database to do their work. I already had a look at many many answers here on stackoverflow and on the internet in general, but I'm not really happy with what I found.
One of the common problems that I see when a database is involved (MySQL in my case) is that the tests are really really long to execute and one possible solution is to use SQLite.
I can't use it everywhere in the tests because some classes uses RAW queries instead of the Doctrine querybuilder to bypass the limitation of unknown datetime functions. SQLite has a different set of functions for date values and this means that to use it in the tests I should rewrite my queries twice, one for MySQL and one for SQLite.
I decided then to stick with MySQL, but creating and dropping the schema every time a test runs takes so much time. I know that the SchemaTool class can handle the creation of a subset of the schema: would be a good solution to create only the tables I really need in the test suite or should I always use the full schema?
Example of the part of code (pseudo-code) I'm trying to test
NotificationManagerClass
constructor(EntityManager)
loadNotifications()
deleteNotification()
updateNotification()
as you can see, I inject the entity manager in the constructor of the class. The class is a service registered in the Symfony container. In the controller I then get this service and use its methods. Inside the methods I use the querybuilder because I must have access to some other services and a repository isn't container-aware. How can I decouple more than this my class? I can't think a way to do it
Upvotes: 0
Views: 2187
Reputation: 41934
You mix a lot of words together, which don't all have the meaning you gave them. A quick summary of the words:
You can't mock everything, because you decided to use raw mysql functions (bad choice)... Always avoid to use mysql queries inside classes. Use either Doctrine (there are a lot of simple ways to bypass the unknown datetime functions) or put the raw queries in the Repository and mock the repository.
In short:
Upvotes: 4
Reputation: 36241
Test involving a database is not a unit test. Your repositories should be tested with integration or functional tests (or acceptance if you write such).
If you have many tests involving database, that probably means you took a wrong turn somewhere, either with:
Might be worth looking into the test pyramid: http://martinfowler.com/bliki/TestPyramid.html
By looking into how to speed up your tests you'll only continue walking the wrong path. The solution is to write more unit tests and less integration tests. Unit tests are fast, independent and isolated, therefore harder to break. Integration tests are more fragile and slow. Look into FIRST properties of unit tests.
Btw: SQLite is a dead end. It doesn't behave like mysql as it doesn't support constraints.
Your example class could be modelled as:
class NotificationManager
{
public function __construct(MyNotificationRepository $repository, MyFabulousService $service)
{}
// ... other methods
}
This way you can inject fakes of both repository and your service and test the NotificationManager in isolation.
There's no much value in unit testing query builders as you'd be testing doctrine instead of your code. It is also hard, since the query class is declared final. You can test if queries return correct results functionally. There's gonna be lot less functional tests needed if you unit test your classes properly.
If you made the MyNotificationRepository an interface, you could even decouple from doctrine.
Upvotes: 2