TDD with Laravel 4, Factory Muff, Ardent, Faker - I'm doing it wrong - validation

I want to write tests for my data models that make sense and work
I've been playing trying to test models like a boss in Laravel (right now I'd say I'm struggling to hold down a position in middle management). I'm using Ardent for my data models. The article referenced above makes it look like Factory Muff is super awesome and would be really handy in facilitating the creation of mock objects.
However, as I dig deeper, I find that Factory Muff is very limited in the data it can provide. It's basically just random words and email addresses. Anything beyond that, it seems I have to write a static method into my data model to generate valid data for the mock object. That doesn't seem very useful, but I'm guessing I'm probably doing it wrong, with a complete misunderstanding of what Factory Muff is all about.
In the data model
Consider this data validation ruleset:
public static $rules = [
'property' => 'required|address',
'name' => 'required|name',
'email' => 'required|email',
'phone' => 'required|phone',
'dob' => 'required|date',
];
Factory Muff seems to be completely ill-suited to generate any of this data beyond the name and email address, unless I want to write a static method that generates data formatted in the correct fashion. This is what I think I have to do with Factory Muff to be able to create a mock object without getting validation errors:
public static $factory = [
'property' => 'call|makeStreetAddress',
'name' => 'string',
'email' => 'email',
'phone' => 'call|makePhone',
'dob' => 'date|makeDate',
];
public static function makeStreetAddress()
{
$faker = \Faker\Factory::create();
return $faker->streetAddress;
}
public static function makePhone()
{
$faker = \Faker\Factory::create();
return $faker->phoneNumber;
}
public static function makeDate()
{
$faker = \Faker\Factory::create();
return $faker->date;
}
This seems pretty verbose, particularly with 10 to 20 fields on a table. I also don't like calling \Faker\Factory::create() in every single static method call (I don't really have a concrete reason, I just don't like it; if you know of a workaround, or if my fear is unfounded, please let me know.)
In the database seeder
I've got database seeding scripts setup that use Faker to generate a bunch of garbage in the database for use in development and testing. It's super intuitive and easy-to-use. For example, this is the seeding script for the above data set.
$faker = \Faker\Factory::create();
$application = Application::create([
'property' => $faker->streetAddress,
'name' => $faker->name,
'email' => $faker->email,
'phone' => $faker->phoneNumber,
'dob' => $faker->dateTimeBetween('-60 years', '-18 years'),
]);
The more I think about what it is I'm doing here, the more I feel like I'm being redundant.
Questions
If I'm seeding the database with garbage data generated by Faker before running my tests, do I even need Factory Muff to create mock objects? Shouldn't I be able to adequately test the codebase using the seed data from the database? Why would I need to mock?
Am I missing the whole point of Faker Muff? Does it have any advantages? It seems to me like little more than a random word generator.
What am I doing wrong? I'm extremely interested in TDD, but it's so daunting. If you noticed any bad practices, or the lack of best practices in my code, please let me know.

I think you are mixing up two different concepts here.
First, the purpose of the data that you create with Faker (in your case, database seeding) is to simulate real life scenarios in your application. For example, if you are developing a blogging platform, you can use it to have some blog posts with tags, user comments and author comments.
When it comes to testing, you can use this in your functional or acceptance tests. For example, if you want to test something like test tag page shows posts tagged with X tag or test user can only delete his own comments, then you could take advantage of Faker data to have some previous posts, tags and comments to work with.
On the other hand, FactoryMuff allows you to quickly create an instance of any given model. Consider the scenario when you are unit testing the validation method for your Post model, so you would have to:
// 1. create new Post
// 2. fill it with data
// 3. try to validate
Having your database seeded is not going to be useful here, since you are going to be creating a new model, and FactoryMuff will do steps 1 and 2 for you. Also, keep in mind that when you are unit testing, you want to do it in isolation, so you shouldn't need to touch your database at all. Instead, you could mock your database object and return fake models and its possible relationships (FactoryMuff to the rescue again).
Finally, I think that maybe you are not seeing the advantages of FactoryMuff functionality because your application may be still small, but you will and won't mind writing a few static methods as your codebase –and your tests– grows.

Related

Laravel Model factories, one to many relationship without database access

For my tests (Laravel 6) I am trying to use factories with make(), in order to bypass the DB operations just creating a new instance of the model.
Now, I am having an headache to make things work with my one to many relationship.
A skeleton of my code just to have an overview:
class Fine extends Model {
// Other things here
public function articles()
{
return $this->hasMany('App\Model\Article');
}
}
where Article does not have any other relationships.
Now, I would like to create a Fine factory to create an instance in which I can parse $fine->articles, all without any database interaction.
Here's the basic Fine factory that works (without using articles):
$factory->define(Fine::class, function (Faker $faker) {
return [
"id" => 10,
Fine::FIELDONE => 'xxx',
Fine::FIELDTWO => 'yyy',
];
});
that I use with  $fine = factory(Fine::class)->make(); .
Now I need to "prefill" articles. I have tried with  hydrate() but it does not work.
I have tried with afterMaking() with different combinations of code:
$factory->afterMaking(Fine::class, function (Fine $fine, Faker $faker) {
$articles = factory(\App\Model\Article::class, 3)->make([\App\Model\Article::FINE_ID => $fine->id]);
// TEST WITH SAVEMANY: $fine->saveMany(factory(\App\Model\Article::class, 10)->make());
// SAME WITH CREATEMANY
// TEST WITH HYDRATE: $multa->articles()->hydrate([$article]);
// $multa->load('articles');
});
Just to make a point I have tried different roads but, I admit, I am a bit lost.
Basically I end up with the classical
Illuminate\Database\QueryException: SQLSTATE[42S02]: Base table or view not found
error, so I am still accessing the database.
Now, I am starting to think that factories are a proper choice only if at the end one is going to store data on the database (I am sure that saveMany() is going to work if I use create() and not make()).
Can you please give me an hint on how to accomplish this task? If factories are not the right way to do it I am more than open to change my choice, too.
Thanks in advance!
Lorenzo
You can set this information on a non existing model instance if you really have to:
$fine->setRelation(
'articles',
factory(\App\Model\Article::class, 3)->make()
)
It depends what you plan on doing with this model and its relationship since there are no IDs set so they are not really related in that way.

Why or when to use Laravel resources/collections

I do not understand why or when to use the resources in Laravel https://laravel.com/docs/7.x/eloquent-resources .
See this controller :
public function show(School $school)
{
// return response()->json($school, 200);
return new SchoolResource($school);
}
The both return solutions returned this kind of response :
{
"data": {
"id": "4f390a7b-3c3f-4c23-9e6a-dd4429cf835d",
"name": "school name",
.......,
The data are the results of a query automatically injected (here : $school).
And same question for a collection of resources. Imagine this controller :
public function index(Request $request)
{
try {
$schools = $this->schoolRepository->all($request->all());
} catch (\Exception $e) {
return response()->json(['message' => 'Bad request'], 400);
}
return SchoolResource::collection($schools);
// return response()->json($schools, 200);
}
If I need to add some fields , I can do that either in the model or in the repository.
I often read that this resource notion is important to understand and to use. But for the moment I do not see when or why I should use it. I certainly must not understand something!
There are a couple of primary reasons to use resources to manage your return values even if your resources don't do anything than pass data through today, you may want it to do something different in the future.
A small list of reasons why resources are really useful
1. Data manipulation, specific to clients (i.e. js applications consuming your api)
If you start manipulating data in your models using mutators (getters / setters). Your internal application now has to work with these constraints. Many times its easier to work with the raw data internally and then just allow your resources to manipulate the data for the client.
2. Conforming to an API specification, such as JSON API v1.0
Whilst you will likely need logic in your application to handle schemas like this, your models and controllers should not. The resource has a critical role here to organise the data for your consumer applications in a compliant fashion.
3. The age old mantra, separation of concerns
This goes hand in hand with point 1, it is not a model or a controllers responsibility to map data to what you expect your consumer applications to receive.
Building on your example
You currently have the following in the show route of your resource controller.
public function show(School $school)
{
// return response()->json($school, 200);
return new SchoolResource($school);
}
This need not change, even if your API specification does (reason 1 and 3).
Adding fields, yes, you'll need to add them to your model but lets use this to actually do something meaningful with your resource.
We've created a migration for a new JSON field ratings. Ratings has a data structure like this:
[
{
name: string,
value: float,
}
]
For reasons such as media scrutiny, we never want to expose all the rating data to our publically available front end consumer apps. Instead we want to provide an average score of all ratings.
Now we could do this in the model, but is it the models responsibility to do this? Not really, the model should be handling and dealing in raw / discreetly modified data only. So is it the controllers responsibility? No, the controller coordinates what should be done and is not interested in specific details or the data.
So where do we do this? Enter your resource that was handily already set up.
class School extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'rating' => $this->getRating(),
];
}
/**
* Mean rating rounded to one decimal place
* #return float
*/
protected function getRating()
{
return (round(array_reduce($this->ratings, function($acc, $curr) {
$acc += $curr['value'];
return $acc;
}, 0) / count($this->ratings), 1);
}
}
The resource is where we have manipulated data specifically for our responses and left our internal data modelling un-touched and clean without pollution from specific nuances of our consuming applications.
More importantly, if you just returned return response()->json($school, 200); from your controller, your data structures will not match and you would be exposing some sensitive data to your front end applications.
Additional (24/12/21)
It's worth noting that if, for example, the data that you are manipulating is required by many different views / resources then we have a number of options.
Traits, create a trait that adds the getRating method. Downside, every class that needs this must import and declare the trait. Upside, your code is dry.
Model scopes, add a scope to your model that does the data processing via SQL (or your DB QL of choice). Downside, slight pollution of the model. Upside, super quick.
Append the data to the model (https://laravel.com/docs/8.x/eloquent-serialization#appending-values-to-json) using an accessor that runs the getRating code to set the data on the model. Upside, clean and usable throughout the application. Downsides pollutes the model a little and data only available in the JSON representation of the model.
Decorate the resource. This allows you to intercept and modify/add to the result of the toArray method in the decorated resource. Upsides not many. Downsides, obfuscated and confusing implementation detail. I wouldn't recommend this approach.
Helper function. Rather than have the method on the resource, create a helper that takes the ratings array and returns the result. Upside, simple implementation. Downsides, none that I can thing of.
So after thinking about this alot I think that I would likely do what I originally wrote in this answer. If I need to re-use I would likely create a helper. If I was concerned about performance I would use a model scope to do the calculations in SQL (if possible, remember it's a JSON field). Taking a step further, if many models require this logic, a trait for those models would be my next step (this only applies if you go down the SQL calculation route).

Is there a shorthand way to save a new database record in Yii2 ActiveRecord?

Is there a shorthand way to save a new record when using Yii2 ActiveRecord?
Here are some examples from the docs:
$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();
and...
Yii::$app->db->createCommand()->insert('customer', ['name' => 'Sam'])->execute();
It would be great if there was a shorthand method like...
Customer::create(['name' => 'Qiang']);
...but I can't seem to find that in the docs. Wondering if I'm missing something. Or would I need to create my own custom ActiveRecord class?
Some ugly way of doing things
(new Customer(['name' => 'Qiang',]))->save();
If you need to return the model
($customer = new Customer(['name' => 'Qiang',]))->save();
".. Or would I need to create my own custom ActiveRecord class?"
Will be good solution for your case, however every time you generate a model from Gii, you'll need not to forget change parent class
Creating record is actually more complicated in real world, because save() does not guarantee that record was saved in database. For example validation may not pass, the record will not be saved, and save() return false. Depending on situation you may want to throw exception in this case, or not.
I suggest to create factory class and encapsulate all necessary conditions, exceptions handling or defaults there - in long term it will be easier to maintain.
$customer = Yii::$app->modelFactory->createCustomer(['name' => 'Qiang']);

How to combine Laravel validation required_if and required_without_all rules?

I have a situation with a subscription form, which must have different validation rules depending on user selection.
I almost complete this, but I'm stuck in a point which need a combination of rules that I think I can't get with predefined laravel rules.
As shown in the following chart, the point is when a user select invoicing preferences, with options Digital and Printed, if user option is Printed I need at least one physical address, so street address field group OR district address fields group must be mandatory.
Mandatory field unless other field is filled can be achieved by required_without_allrule, so I've trying with no success, a combination of required_if and required_without_allrules, like the following example:
public function rules()
{
return [
...
'invoicing_preferences' => 'required',
'invoicing_email' => 'email|required_if:invoicing_preferences,digital',
'invoicing_street_name' => 'string|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_street_number' => 'number|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_street_flat' => 'number|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_street_dep' => 'alpha_num|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_district' => 'alpha_num|required_if:invoicing_preferences,printed|required_without_all:invoicing_street_name, invoicing_street_number; invoicing_street_flat,invoicing_street_dep',
'invoicing_parcel' => 'alpha_num|required_if:invoicing_preferences,printed|required_without_all:invoicing_street_name, invoicing_street_number; invoicing_street_flat,invoicing_street_dep',
...
];
}
This combination doesn't work because always results in the required_with_allrule no matter if I've checked digital at the first point.
The rules() method is a method that is expected to return array of rules. Why would I write about such an obvious thing? Well, insert any kind of validation logic inside it, which means that it can also do some evaluation of posted data and gradually build up the returning array.
public function rules()
{
$this; // holds information about request itself with all the data POST-ed
if (something) {
return []; // something is true...
}
return []; // default behaviour (ehm, something is not true)
}
Another similar approach is to use multiple arrays and in the end merge them together (build them up). Which may result in nicer code. Also do not be afraid of using one or two private methods to clean up the code.

Zend Framework Model

is it posible to have a true Model Layer in PHP like in Ruby on Rails? With Zend Framework you can create a Model but this is a class. As I know you have to write all the logic by myself.
Solutions?
True, in Zend Framework you need to declare classes for the database tables you'd like to access. But the rest can be done implicitly, if the default behavious is sufficient. The following is a valid and functional model class (compare to http://framework.zend.com/manual/en/zend.db.table.html#zend.db.table.introduction):
class bugs extends Zend_Db_Table_Abstract
{
// table name matches class name
}
This should let you access the table named "bugs", e.g.:
$table = new bugs();
$data = array(
'created_on' => '2007-03-22',
'bug_description' => 'Something wrong',
'bug_status' => 'NEW'
);
$table->insert($data);
Again, the example was taken directly from the documentation mentioned above.
Or since 1.8.x there is a DataMapper pattern used for models (see quickstart in manual)
i wrote a script that might suite your needs.
http://code.google.com/p/zend-db-model-generator/

Resources