I have a class "Configuration" that has a method "getConfig" that reads a configuration file "config.ini" where I have all the app configs (database credentials and host, some apis keys, .....)
For now I have this unit test that tests if the database entry exists in the config file and at the same time confirms that the array returned by the method "getConfig" has the key "database":
function testConfigFileHasDatabaseEntry()
{
$configuration = Configuration::getInstance();
$arrConfig = $configuration->getConfig();
$this->assertArrayHasKey('database', $arrConfig);
}
I have also another unit test that confirms that "getConfig" returns a variable of type array.
My question is the following:
In terms of unit testing best practices, in this case is this test enough to confirm that the function getConfig is well tested or it is better to confirm that every key exists in the config file. I think confirming that all entries are in the config file maybe falls under another category of testing, but I want to be sure.
Let me know what is the best practice in this case.
Base on the answer of gcontrollez I realized it is better to post the class source code:
<?php
/**
* Loads the configuration file
*/
namespace Example\Lib;
class Configuration {
private static $instance;
private $arrConfig;
/**
* Constructor: loads the config file into property arrConfig and dies if unable
* to load config file
*
* #return void
*/
private function __construct()
{
$this->arrConfig = parse_ini_file("config/settings.ini", true);
if ($this->arrConfig == false){
die("Cannot load configuration file");
}
}
/**
* returns an instance of this singleton class
*
* #return Configuration
*/
public static function getInstance()
{
if (self::$instance == null){
self::$instance = new Configuration();
}
return self::$instance;
}
/**
* Getter for the property arrConfig
*
* #return array:
*/
public function getConfig() {
return $this->arrConfig;
}
}
Thanks
You are not unit testing the getConfig method. You are just checking that its result contains certain data. This is perfectly valid and can be useful but is not a unit test.
If you wanted to unit test the getConfig method, you should check it's behaviour with each possible config.ini file. I suppose you are using the parse_ini_file function inside getConfig. So in order to unit test the getConfig method you should check all the possible cases you think that can happen:
- testConfigFileIsLoaded
- testExceptionThrownIfConfigFileFailsToLoad
The parse_ini_file returns an array or false so those are the cases that you should check.
For that you need to be able to change the .ini file to be used. If it's harcoded inside the Configuration class you wont be able to do that.
Related
example:
path: '/example'
defaults:
_controller: '\Drupal\example\Controller\ExampleController::content'
requirements:
_custom_access: '\Drupal\example\Controller\ExampleController::access'
This custom_access checker will be executed only when someone call mywebsite.domain/example.
But I want that this controller check all urls, run independent of path.
How can I create an independent custom access controller?
The idea for preventing routing access to a very low level (Kernel one to be precise), is to register a EventSubscriber service, subscribing to the REQUEST KernelEvent.
First of all, you will need to create a new custom module.
Once done, you will be able to create a new my_module.services.yml file which will declare a new EventSubscriber
services:
my_module.subscriber:
class: Drupal\my_module\EventSubscriber\MyCustomSubscriber
tags:
- { name: event_subscriber}
Then, create the class referenced above in my_module/src/EventSubscriber/MyCustomSubscriber.php.
Here is a tiny example which checks if the current user is logged-in before accessing any page, otherwise redirect on the login page. This following code is not complete (see the last reference for a better explanation) but it shows you the basics (subscription to the event, dependency injection, event redirection, ...)
<?php
namespace Drupal\my_module\EventSubscriber;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MyCustomSubscriber implements EventSubscriberInterface {
/**
* The current route match.
*
* #var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Class constructor.
*
* #param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {#inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = ['isLoggedIn'];
return $events;
}
/**
* It verify the page is requested by a logged in user, otherwise prevent access.
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* A response for a request.
*/
public function isLoggedIn(GetResponseEvent $event) {
$route_name = $this->routeMatch->getRouteName();
// Don't run any assertion on the login page, to prevent any loop redirect.
// If intend to be used on a production project, please #see
// https://www.lucius.digital/en/blog/drupal-8-development-always-redirect-all-logged-out-visitors-to-the-login-page for a better implementation.
if ($route_name === 'user.login') {
return;
}
if (\Drupal::currentUser()->isAnonymous()) {
$dest = Url::fromRoute('user.login')->toString();
$event->setResponse(RedirectResponse::create($dest));
}
}
}
To go further, you may read those explanations of registering event subscribers & some use case:
Responding to Events in Drupal 8
How to Register an Event Subscriber in Drupal 8
Always redirect all logged out visitors to the login page
I hope it will help you.
I moved some code from a model to a Lib in my app, because it can be used from 2 models and is not database related. It is an uploadhandler, which is used from my two models. It is placed at /app/Lib/UploadHandler.php
That works. Hurray...
But: How can I write Unit Tests for my Lib? It is a lib not 3rd party classes which would belong into the vendor folder. I created a TestClass in an new folder "Lib" in
/app/Test/Case/Lib/UploadHandlerTest.php
What I do so far is:
App::uses('UploadHandler', 'Lib');
/**
* UploadHandler Test Case
*
*/
class UploadHandlerTestCase extends CakeTestCase
{
/**
* Fixtures
*
* #var array
*/
public $fixtures = array();
/**
* setUp method
*
* #return void
*/
public function setUp() {
parent::setUp();
$this->UploadHandler = ClassRegistry::init('UploadHandler');
}
/**
* tearDown method
*
* #return void
*/
public function tearDown() {
unset($this->UploadHandler);
parent::tearDown();
}
public function testHandleFileUpload() {....
which gives me errors like:
MissingTableException
Table sang_upload_handlers for model UploadHandler was not found in datasource test.
Test case: UploadHandlerTestCase(testHandleFileUpload)
But - hey I don't need the database! All my methods run just with the data I give the public methods.
What would be the proper initialization of my test class?
Thank you for any help!
Calamity Jane
So in case anybody except me is interested in it I found a way to make it work like I want.
First the folder structure:
Class location:
app/Lib/MyLib.php
Test class location:
app/Test/Case/Lib/MyLibTest.php
By this you can see the Test for your library in the CakePHP browser webinterface.
Second: How to prevent the error about dabase tables.
The answer is quite simple: Just let your testclass extend not the CakeTestCase, but use the original PHPUnit_Framework_TestCase and you will have no trouble with missing tables.
Duh....
Example:
require_once DS.'var'.DS.'www'.DS.'myproject'.DS.APP_DIR.DS.'/Lib/UploadHandler.php';
/**
* UploadHandler Test Case
*
*/
class UploadHandlerTestCase extends PHPUnit_Framework_TestCase
{....
I was happy to help myself ;) I guess a lot of sleep during the change of the year did the trick ;)
Calamity Jane
I have a unit test that I am trying to get the validation to succeed on.
Basically, when I call badMockedSecureFile.validate(), it is not doing what I expect, and that is failing validation for the two fields, encryptedFileName and encryptedFileData.
When I break in the debugger, I simply get a null value for badMockedSecureFile.errors in the subsequent assert. Here are my two files:
Any input would be greatly appreciated. I couldn't find an exact similar question. I'm using grails 2.2.4 with Oracle JDK 1.7.0_25 if that matters any.
EDIT: I just wanted to note that I removed the mockForConstraintTests call and it seems to be working now. I get this feeling I didn't RTFM somewhere and this behaviour changed in unit testing, or is something else going on?
SecureFile.groovy
class SecureFile implements Serializable {
/**
* An unencrypted version of the file name. This file name is unencrypted
* when the appropriate password and key combo is used and it is never
* persisted to the database for security (see transients below).
*/
String fileName
/**
* Unencrypted version of the file data. Never persisted to the
* database for security (see transients below).
*/
byte[] fileData
String encryptedFileName
byte[] encryptedFileData
Date dateAdded
Date dateUpdated
Date dateDeleted
static constraints = {
encryptedFileName(nullable: false, blank: false)
encryptedFileData(nullable: false)
}
static transients = ["fileName", "fileData"]
static belongsTo = [user: User]
}
SecureFileTests.groovy
import static org.junit.Assert.*
import grails.test.mixin.*
import grails.test.mixin.support.*
import org.junit.*
/**
* See the API for {#link grails.test.mixin.support.GrailsUnitTestMixin} for usage instructions
*/
#TestFor(SecureFile)
class SecureFileTests {
static final String SAMPLE_PDF_FILE = "fileEncryptionTestSample.pdf"
void testConstraints() {
def samplePdfFile = new FileInputStream(SAMPLE_PDF_FILE)
// Not really encrypted for this mock.
def mockedSecureFile = new SecureFile(
encryptedFileName: "--Not-Really-Encrypted--",
encryptedFileData: samplePdfFile.getBytes()
)
mockForConstraintsTests(SecureFile, [mockedSecureFile])
// Validation should fail if both properties are null.
def badMockedSecureFile = new SecureFile()
assert !badMockedSecureFile.validate()
assert "nullable" == badMockedSecureFile.errors["encryptedFileName"].code
assert "nullable" == badMockedSecureFile.errors["encryptedFileData"].code
}
}
Remove code from badMockedSecureFile.errors["encryptedFileName"].code .
You will get as you expected.
mockForConstraintsTests(SecureFile)
// Validation should fail if both properties are null.
def badMockedSecureFile = new SecureFile()
assert !badMockedSecureFile.validate()
assert "nullable" == badMockedSecureFile.errors["encryptedFileName"]
assert "nullable" == badMockedSecureFile.errors["encryptedFileData"]
I have an Entity called Game with a related Repository called GameRepository:
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue() {
$this->slug = $repo->createUniqueSlugForGame();
}
}
In the prePersist method, I need to ensure that the Game's slug field is unique, which requires a database query. To do the query, I need access to the EntityManager. I can get the EntityManager from inside GameRepository. So: how do I get the GameRespository from a Game?
You actually can get the repository in your entity and only during a lifecycle callback. You are very close to it, all you have to do is to receive the LifecycleEventArgs parameter.
Also see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue( LifecycleEventArgs $event ) {
$entityManager = $event->getEntityManager();
$repository = $entityManager->getRepository( get_class($this) );
$this->slug = $repository->createUniqueSlugForGame();
}
}
PS. I know this is an old question, but I answered it to help any future googlers.
You don't. Entities in Doctrine 2 are supposed to not know of the entity manager or the repository.
A typical solution to the case you present would be to add a method to the repository (or a service class) which is used to create (or called to store) new instances, and also produces a unique slug value.
you can inject the doctrine entity manager in your entity
(using JMSDiExtraBundle)
and have the repository like this:
/**
* #InjectParams({
* "em" = #Inject("doctrine.orm.entity_manager")
* })
*/
public function setInitialStatus(\Doctrine\ORM\EntityManager $em) {
$obj = $em->getRepository('AcmeSampleBundle:User')->functionInRepository();
//...
}
see this : http://jmsyst.com/bundles/JMSDiExtraBundle/1.1/annotations
In order to keep the logic encapsulated without having to change the way you save the entity, instead of the simple prePersist lifecycle event you will need to look at using the more powerful Doctrine events which can get access to more than just the entity itself.
You should probably look at the DoctrineSluggableBundle or StofDoctrineExtensionsBundle bundles which might do just what you need.
I am having an issue with EF 4.1 using "Code First". Let me setup my situation before I start posting any code. I have my DBContext class, called MemberSalesContext, in a class library project called Data.EF. I have my POCOs in a seperate class library project called Domain. My Domain project knows nothing of Entity Framework, no references, no nothing. My Data.EF project has a reference to the Domain project so that my DB context class can wire up everything in my mapping classes located in Data.EF.Mapping. I am doing all of the mappings in this namespace using the EntityTypeConfiguration class from EntityFramework. All of this is pretty standard stuff. On top of Entity Framework, I am using the Repository pattern and the Specification pattern.
My SQL Server database table has a composite primary key defined. The three columns that are part of the key are Batch_ID, RecDate, and Supplier_Date. This table as an identity column (database generated value => +1) called XREF_ID, which is not part of the PK.
My mapping class, located in Data.EF.Mapping looks like the following:
public class CrossReferenceMapping : EntityTypeConfiguration<CrossReference>
{
public CrossReferenceMapping()
{
HasKey(cpk => cpk.Batch_ID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.Supplier_Date);
Property(p => p.XREF_ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("wPRSBatchXREF");
}
}
My MemberSalesContext class (inherits from DBContext) looks like the following:
public class MemberSalesContext : DbContext, IDbContext
{
//...more DbSets here...
public DbSet<CrossReference> CrossReferences { get; set; }
//...more DbSets here...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
//...more modelBuilder here...
modelBuilder.Configurations.Add<CrossReference>(new CrossReferenceMapping());
//...more modelBuilder here...
}
}
I have a private method in a class that uses my repository to return a list of objects that get iterated over. The list I am referring to is the outermost foreach loop in the example below.
private void CloseAllReports()
{
//* get list of completed reports and close each one (populate batches)
foreach (SalesReport salesReport in GetCompletedSalesReports())
{
try
{
//* aggregate sales and revenue by each distinct supplier_date in this report
var aggregates = BatchSalesRevenue(salesReport);
//* ensure that the entire SalesReport breaks out into Batches; success or failure per SalesReport
_repository.UnitOfWork.BeginTransaction();
//* each salesReport here will result in one-to-many batches
foreach (AggregateBySupplierDate aggregate in aggregates)
{
//* get the batch range (type) from the repository
BatchType batchType = _repository.Single<BatchType>(new BatchTypeSpecification(salesReport.Batch_Type));
//* get xref from repository, *if available*
//* some will have already populated the XREF
CrossReference crossReference = _repository.Single<CrossReference>(new CrossReferenceSpecification(salesReport.Batch_ID, salesReport.RecDate, aggregate.SupplierDate));
//* create a new batch
PRSBatch batch = new PRSBatch(salesReport,
aggregate.SupplierDate,
BatchTypeCode(batchType.Description),
BatchControlNumber(batchType.Description, salesReport.RecDate, BatchTypeCode(batchType.Description)),
salesReport.Zero_Sales_Flag == false ? aggregate.SalesAmount : 1,
salesReport.Zero_Sales_Flag == false ? aggregate.RevenueAmount : 0);
//* populate CrossReference property; this will either be a crossReference object, or null
batch.CrossReference = crossReference;
//* close the batch
//* see PRSBatch partial class for business rule implementations
batch.Close();
//* check XREF to see if it needs to be added to the repository
if (crossReference == null)
{
//*add the Xref to the repository
_repository.Add<CrossReference>(batch.CrossReference);
}
//* add batch to the repository
_repository.Add<PRSBatch>(batch);
}
_repository.UnitOfWork.CommitTransaction();
}
catch (Exception ex)
{
//* log the error
_logger.Log(User, ex.Message.ToString().Trim(), ex.Source.ToString().Trim(), ex.StackTrace.ToString().Trim());
//* move on to the next completed salesReport
}
}
}
All goes well on the first iteration of the outer loop. On the second iteration of the outer loop, the code fails at _repository.UnitOfWork.CommitTransaction(). The error message returned is the following:
"The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges."
In this situation, the database changes on the second iteration were not committed successfully, but the changes in the first iteration were. I have ensured that objects in the outer and inner loops are all unique, adhering to the database primary keys.
Is there something that I am missing here? I am willing to augment my code samples, if it proves helpful. I have done everything within my capabilities to troubleshoot this issue, minus modifying the composite primary key set on the database table.
Can anyone help??? Much thanks in advance! BTW, sorry for the long post!
I am answering my own question here...
My issue had to do with how the composite primary key was being defined in my mapping class. When defining a composite primary key using EF Code First, you must define it like so:
HasKey(cpk => new { cpk.COMPANYID, cpk.RecDate, cpk.BATTYPCD, cpk.BATCTLNO });
As opposed to how I had it defined previously:
HasKey(cpk => cpk.COMPANYID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.BATTYPCD);
HasKey(cpk => cpk.BATCTLNO);
The error I was receiving was that the ObjectContext contained multiple elements of the same type that were not unique. This became an issue in my UnitOfWork on CommitTransaction. This is because when the mapping class was instanciated from my DBContext class, it executed 4 HasKey statements shown above, with only the last one for property BATCTLNO becoming the primary key (not composite). Defining them inline, as in my first code sample above, resolves the issue.
Hope this helps someone!