@yeasir_arafat Why do you have a Laravel app that speaks to a Laravel app? Your users now how to wait for two sequential network calls to complete instead of just one. You’ve literally doubled your response times.
Unit testing of an app with shared db
Hi Artisans,
I have 2 laravel apps. One (let's say app XX) serves as the main api backend. The other(lets say app YY), has an intertia front end and laravel backend. The apps talk to each other via laravel's HTTP wrapper. The apps also share one model(Author model) between them. Since XX is the main app, the authors table resides in the XX database and i am sharing the DB connection from app YY for the Author model there via the $connection property in the model.
The challenge is, I am not sure how to do unit tests in YY app when the tests involve the Author model. The relevant migration and factory is in the XX app. I can probably copy the migration, factory to YY app and locally that may work since i can get both the apps running while unit testing, so that app YY test can reach the authors table in app XX. But how do i make the tests work in bitbucket pipeline? In pipeline only the app being tested is containerized and runs the tests using it's own db. But since authors table does not exist there, the tests will fail.
Anyone has any experience in similar setup?
P.S.; I am requesting ideas to solve an interesting problem. Not opinions about the setup of the microservice ecosystem only. Although I will definitely welcome that, if you can also share ideas about the main problem i am trying to solve :).
Tests are only as good as the developer who wrote them. Having said that, any experienced developer would agree that TDD and test coverage is imperative. I would never dream of running anything in production without test coverage.
@yeasir_arafat Yes, I actually do have this similar architecture for an application. I have a main API that is consumed by a few applications. Two of those applications are in-house (or intranet only) used by internal employees. The other app is a customer portal that is publicly accessible. The reason why it has its own Laravel app is for the sake of security. We're talking about an application that is responsible for managing projects worth multi-millions of dollars. We can configure our main API (and firewall) to only accept incoming requests from that public facing server, which basically serves as a proxy or service layer. The database and domain logic is all on our main API, buried behind a firewall. If the public API was ever compromised, there would be no data breach (or minimal) and very little code. This allows us to be very selective in what we expose to the internet.
Again, having all our data and logic in the main API, we really don't need to worry about shared unit testing. The portal API is basically just a delegating relay service. For form validation, I wrote some middleware for Laravel's HTTP client, which intercepts 422 responses, and basically hydrates a validator with all the errors and automatically bubbles it up to the client. It works very smoothly for passing errors directly through the service.
After saying all this, in our case, there is a small need to unit test across multiple databases, such as the user models for external clients/customers. What you were thinking is along the same lines as what we have done. Even though the external users are within another database/app, I created a migration for them that is only ran if the environment is for testing:
public function up()
{
if (\App::environment('testing'))
{
Schema::connection(config('database.portal'))->create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->string('password');
$table->timestamps();
});
}
}
As you can see, it only runs if the environment is testing, which would be when phpunit is executed. This will create the migration for testing, but keep it out of any other environment deployment. You can also see on the Schema facade I make a call to connection(), which uses another database connection/configuration so I don't have a collision with the same users table name. This connection would also be set to localhost to simulate the external database for the tests. So the actual migration of the external users table is controlled by the portal application, but here we've faked it for testing. You'd also want to create an entry in phpunit.xml for the database connection. I use an in memory database, so something like:
<env name="PORTAL_DB_CONNECTION" value="portal_memory_testing"/>
<env name="PORTAL_DB_DATABASE" value=":memory:"/>
And then add the database configuration to your config/database.php
I then created an abstract model class to use for any of the external models where they override the constructor and set the database connection:
abstract class PortalModel extends Model
{
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->setConnection(config('database.portal'));
}
}
Now this allows me create model classes such as:
class ClientUser extends PortalModel implements ApplicationUser
{
use HasFactory;
public function projects()
{
$this->connection = env('DB_CONNECTION');
return $this->belongsToMany(Project::class, 'project_user');
}
And create factories:
class ClientUserFactory extends Factory
{
protected $model = ClientUser::class;
protected $connection = 'portal';
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'password' => $this->faker->lexify('?????????'),
];
}
and perform tests:
class ClientUserTest extends TestCase
{
/** @test */
public function it_can_belong_to_many_projects()
{
$user = ClientUser::factory()->has(Project::factory()->count(3))->create();
$this->assertEquals(3, $user->projects->count());
}
This might seem convoluted at first, but once its set up, you don't have to think about it any longer. This app has over 2000 tests and growing, and I haven't given this a second thought in over a year.
Hopefully this helps you.
Please or to participate in this conversation.