It is not quite clear to me when I should use integrations and unit test
If I want to test the following code without making any http requests, should I use an integration test or unit ?
When a thread is created, the activity is recorded and an email event is fired
ThreadObserver extends Observer
{
public function created(Thread $thread)
{
Activity::record($thead);
event(new ThreadWasCreated($thread);
}
}
class RecordsActivityTest
{
public function it_records_the_acitivty_when_a_thread_is_created()
{
Thread::factory()->create();
$this->assertDatabaseHas('activities', […]);
}
}
Another example, I want to test that give a query it returns the expected results .
class Search
{
public function __construct(protected Index $index)
{
}
public function handle($query)
{
$this->index->search($query);
// more code
}
}
class SearchTest
{
public function it_returns_the_expected_result()
{
$search = new Search(new Index);
$results = $search->handle(“some query”);
$this->assertEquals(“some
Data”);
}
}
Do I need a unit test, to test the Search class or an integration test because it is dependent on another class ?
The first example, I'd most likely write an integration test, but instead of testing if the database has a value, I'd just mock the event https://laravel.com/docs/8.x/mocking#event-fake and assert that the event was dispatched.
The second example however, it could go either way. You can write a unit test and just mock everything OR, you could write an integration test so you can use the Laravel helpers, specifically the https://laravel.com/docs/8.x/mocking#http-fake if you're testing an API, otherwise mockery if you're testing and SDK for example.
Related
I am testing a service which heavily relies on project reactor.
For many tests I am mocking the return value of the component responsible for API calls.
The tests are split over multiple files.
When I run the tests of one file, they are green, but when I execute all of the test files at once, some tests fail, with the error message indicating that the mocking did not succeed (Either the injected component returned null, or the implementation of the actual component is invoked).
In the logs, there is no information about the mocking failing.
A code example:
interface API {
Flux<Bird> getBirds();
}
#Component
class BirdWatcher {
API api;
BirdWatcher(API api) {
this.api = api;
}
Flux<Bird> getUncommonBirds() {
return api.getBirds() // Although this is mocked in the test, in some runs it returns `null` or calls the implementation of the actual component
.filter(Bird::isUncommon);
}
}
#SpringBootTest
class BirdWatcherTests {
#Autowired
BirdWatcher birdWatcher;
#MockBean
API api;
#Test
void findsUncommonBirds() {
// Assemble
Bird birdCommon = new Bird("Sparrow", "common");
Bird birdUncommon = new Bird("Parrot", "uncommon");
Mockito.when(api.getBirds()).thenReturn(Flux.just(birdCommon, birdUncommon));
// Act
Flux<Bird> uncommonBirds = birdWatcher.getUncommonBirds();
// Assert
assertThat(uncommonBirds.collectList().block().size(), equalTo(1));
}
}
For me the issue seems like a race condition, but I don't know where and how this might happen, and how I can check and fix this.
I am using spring-boot-test:2.7.8, pulling in org.mockito:mockito-core:4.5.1 org.mockito:mockito-junit-jupiter:4.5.1, and org.junit.jupiter:junit-jupiter:5.8.2, with gradle 7.8.
For reactor, spring-boot-starter-webflux:2.7.8, depending on reactor:2.7.8.
Trying to get to grips with Mocking and test cases, I want to test that a Mailable TestMail is sent from company#company.com, the documentation provides hasTo, hasCc, and hasBcc but doesn't look like it uses something like hasFrom. Is there any solutions to this?
https://laravel.com/docs/9.x/mocking#mail-fake
public function testEmailAlwaysFrom()
{
Mail::fake();
Mail::to('foo#bar.com')->send(new TestMail);
Mail::assertSent(TestMail::class, function ($mail) {
return assertEquals('company#company.com', $mail->getFrom());
// return $mail->hasTo($user->email) &&
// $mail->hasCc('...') &&
// $mail->hasBcc('...');
});
}
MailFake doesn't provide hasFrom method in the class and therefore will return false.
The workaround below however doesn't work when using the environmental variable MAIL_FROM_ADDRESS, ->from() has to be called within build().
A couple of GitHub issues have been reported suggesting a workaround below:
https://github.com/laravel/framework/issues/20056
https://github.com/laravel/framework/issues/20059
public function testEmailAlwaysFrom()
{
Mail::fake();
Mail::to('foo#bar.com')
->send(new TestMail);
Mail::assertSent(TestMail::class, function ($mail) {
$mail->build(); // <-- workaround
return $mail->hasTo('foo#bar.com') and
$mail->hasFrom('company#company.com');
});
}
I'm trying to refactor a class and having problems testing it when I place certain code in the __constructor method and it throws an error that table not found within the test but works outside of tests.
I know this means that in the testing environment the table has yet to be created and although I'm using RefreshDatabase within the test it appears that at the point the class I'm testing initialises and attempts to access the database it's not ready. So I'm either doing something in the constructor I shouldn't or I'm missing something in my test structure.
Here's the basics of the class I'm tryting to test:
class PlayerRounds
{
use EclecticPresenter;
private RoundRepository $roundRepository;
private CourseRepository $courseRepository;
private $courseHoles;
public function __construct(RoundRepository $roundRepository, CourseRepository $courseRepository)
{
$this->roundRepository = $roundRepository;
$this->courseRepository = $courseRepository;
$this->init();
}
private function init()
{
$this->courseHoles = $this->courseRepository->all();
}
/**
* generates eclecic rounds for each league the player is in
* #param Player $player
*/
public function getAllEclecticRounds(Player $player)
{
$allEclecticRounds = collect();
$leagues = $player->league()->where('league_type', 'eclectic')->get();
$leagues->each(function ($league) use ($player, $allEclecticRounds) {
$newRound = $this->getPlayerEclecticRound($player, $league);
$allEclecticRounds->put($league->id, $newRound);
});
return $allEclecticRounds;
}
The test fails at the init() method. The fetch $this->courseHoles = $this->courseRepository->all(); the the test fails with a table not found error if it's within the constructor, It works if I place this piece of code within each method that needs it but means I call it often rather than once.
Here's my test:
class PlayerRoundsTest extends TestCase
{
use RefreshDatabase;
use EclecticTestHelper;
use WithFaker;
private $playerRounds;
protected function setUp(): void
{
parent::setUp();
$this->seed(CourseTableSeeder::class);
$this->playerRounds = app()->make(PlayerRounds::class);
}
/**
* #test
* #covers PlayerRounds::getAllEclecticRounds
* #description:
*/
public function it_returns_an_eclectic_round_for_all_leagues()
{
$player = Player::factory()->has(League::factory()->count(3))->create();
foreach ($player->league()->get() as $league) {
for ($x = 0; $x <= 3; $x++) {
$this->createScores($league, $player);
}
}
$result = $this->playerRounds->getAllEclecticRounds($player);
$this->assertCount(3, $result);
$result->each(function($collection) {
$this->assertCount(1, $collection);
});
Are there any ideas how I can initiate the class correctly and get the test set up correct and ensure the database is ready for the test. I assumed using RefreshDatabase was the correct approach and I had things in the correct order.
Thank you
**update
If I change the constructor to this:
public function __construct(RoundRepository $roundRepository, CourseRepository $courseRepository)
{
$this->roundRepository = $roundRepository;
$this->courseRepository = $courseRepository;
}
and then place the code that calls on the database to the method used in the test back to this:
public function getPlayerEclecticRound(Player $player, League $league, $maxDate = null)
{
// FIXME: Initiate at start in constructor but fails in tests
$this->courseHoles = $this->courseRepository->all();
//rest of code removed for brevity
}
This then passes the test.
This class needs the data in $this->courseHoles for a number of methods to work so I'm aiming to just call this once at initialization rather than every time I access the method as it is now but can' get it to work in a testing environment.
note I'm using a mysql database on the server but a sqllite memory database in testing
###update
Ok, after a bit of playing around the error is being caused by the loading of a custom artisan command I created. That command class has a dependancy of another class which in turn calls on the class I'm tresting.
I removed the command from Kernel.php as follows:
protected $commands = [
Inspire::class,
FixtureReminder::class,
SmsFixtureReminder::class,
UpdateMigrationTable::class,
// EclecticUpdate::class,
// MatchplayUpdate::class,
CleanTemporaryFiles::class,
AuthPermissionCommand::class
];
So - am I right in assuming for test purposes this is going to be impossible to isolate without changing this each time? This all relates to dependencies and the order in which CreateApplication works but I don't know enough to work around this.
Redacting unit tests, I am confronted to this problem. A piece of code that I want to test catches a \Doctrine\DBAL\Exception\RetryableException. The first constructor in the classes chain is the one of DriverException and is built like this :
/**
* #param string $message The exception message.
* #param \Doctrine\DBAL\Driver\DriverException $driverException The DBAL driver exception to chain.
*/
public function __construct($message, \Doctrine\DBAL\Driver\DriverException $driverException)
{
$exception = null;
if ($driverException instanceof Exception) {
$exception = $driverException;
}
parent::__construct($message, 0, $exception);
$this->driverException = $driverException;
}
I feel like I am confronted to the problem of the egg and the chicken, here. How can I instanciante a class that takes an instance of itself as mandatory argument in the first place ?
Note: I won't mark this auto-response as a solution, it is more a workaround.
Instead of throwing the right exception in my unit test mock, I have created a simpler one, extending Exception but still implementing the original interface RetryableException, as it's the interface that is caught in the code I am testing. While not being what I wanted to do, it does the job in my precise case.
Here is how I have an actual instance of DriverException in my unit tests, using an anonymous class instead of a mock:
<?php
declare(strict_types=1);
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
use PHPUnit\Framework\TestCase;
final class MyTest extends TestCase
{
// ... the rest of the test case
private function getDriverExceptionWithCode(int $code): DriverException
{
$theDriverException = new class($code) extends \Exception implements TheDriverException {
public function __construct(int $code)
{
parent::__construct('oh no, you broke it :(', $code);
}
public function getSQLState(): ?string
{
return null;
}
};
return new DriverException($theDriverException, null);
}
}
In my case I needed to unit test a situation where the code is catching a DriverException with a specific code, but you can extend the code as you wish, or make it simpler. The only thing you need is to implement getSQLState, after all.
Hope this helps whoever stumbles on this question from their favorite search engine.
I'm writing a unit test for a Flutter method that calls an async method and then returns, leaving the async to complete as and when. My test fails "after it had already completed".
Here's my test:
test('mark as viewed', () {
final a = Asset();
expect(a.viewed, false);
a.markAsViewed();
expect(a.viewed, true);
});
and here's the method it's testing:
void markAsViewed() {
viewed = true;
Repository.get().saveToStorage();
}
The saveToStorage() method is an async that I just leave to execute in the background.
How do I make this work? The test failure tells me Make sure to use [expectAsync] or the [completes] matcher when testing async code. but I can't see how to do that. Can anyone explain or else point me to the right documentation please? I can't find anything about how to handle these asyncs when it's not a Future that's being returned, but just being left to complete separately.
To be clear - this unit test isn't about testing whether it's saved to storage, just a basic test on setting viewed to be true.
Edited
The error is as follows:
package:flutter/src/services/platform_channel.dart 319:7 MethodChannel.invokeMethod
===== asynchronous gap ===========================
dart:async _asyncErrorWrapperHelper
package:exec_pointers/asset_details.dart Repository.saveToStorage
package:exec_pointers/asset_details.dart 64:22 Asset.markAsViewed
test/asset_details_test.dart 57:9 main.<fn>.<fn>
This test failed after it had already completed. Make sure to use [expectAsync]
or the [completes] matcher when testing async code.
This code is tightly coupled to implementation concerns that make testing it in isolation difficult.
It should be refactored to follow a more SOLID design with explicit dependencies that can be replaced when testing in isolation (unit testing)
For example
class Asset {
Asset({Repository repository}) {
this.repository = repository;
}
final Repository repository;
bool viewed;
void markAsViewed() {
viewed = true;
repository.saveToStorage();
}
//...
}
That way when testing a mock/stub of the dependency can be used to avoid any unwanted behavior.
// Create a Mock Repository using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockRepository extends Mock implements Repository {}
main() {
test('mark as viewed', () {
final repo = MockRepository();
// Use Mockito to do nothing when it calls the repository
when(repo.saveToStorage())
.thenAnswer((_) async => { });
final subject = Asset(repo);
expect(subject.viewed, false);
subject.markAsViewed();
expect(subject.viewed, true);
//
verify(repo.saveToStorage());
});
}
The test should now be able to be exercised without unexpected behavior from the dependency.
Reference An introduction to unit testing
Reference Mock dependencies using Mockito
Reference mockito 4.1.1